diff --git a/.gitignore b/.gitignore index 0f45edc0..46441edc 100644 --- a/.gitignore +++ b/.gitignore @@ -147,7 +147,6 @@ dmypy.json .pyre/ # mac env -.DS_Store bin # register stuff diff --git a/AUTHORS.md b/AUTHORS.md index 4a487968..f97e0934 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -33,7 +33,6 @@ * PureDreamer - Developer * ShiZinDle - Developer * YairEn - Developer - * LiranCaduri - Developer * IdanPelled - Developer # Special thanks to diff --git a/app/babel_mapping.ini b/app/babel_mapping.ini index d28e505a..726e77f1 100644 --- a/app/babel_mapping.ini +++ b/app/babel_mapping.ini @@ -1,6 +1,7 @@ # Extraction from Python source files [python: **.py] -# Extraction from Jinja2 HTML and text templates +# Extraction from Jinja2 HTML and j2 templates [jinja2: **/templates/**.html] +[jinja2: **/templates/**.j2] extensions = jinja2.ext.i18n,jinja2.ext.autoescape,jinja2.ext.with_ diff --git a/app/config.py.example b/app/config.py.example index d296d02e..a5b7325d 100644 --- a/app/config.py.example +++ b/app/config.py.example @@ -67,7 +67,6 @@ email_conf = ConnectionConfig( JWT_KEY = "JWT_KEY_PLACEHOLDER" JWT_ALGORITHM = "HS256" JWT_MIN_EXP = 60 * 24 * 7 - templates = Jinja2Templates(directory=os.path.join("app", "templates")) # application name diff --git a/app/database/models.py b/app/database/models.py index 390896c4..67fa6a0f 100644 --- a/app/database/models.py +++ b/app/database/models.py @@ -79,8 +79,7 @@ class User(Base): back_populates="user", ) comments = relationship("Comment", back_populates="user") - tasks = relationship( - "Task", cascade="all, delete", back_populates="owner") + tasks = relationship("Task", cascade="all, delete", back_populates="owner") features = relationship("Feature", secondary=UserFeature.__tablename__) oauth_credentials = relationship( diff --git a/app/database/schemas.py b/app/database/schemas.py index 5b9ad02e..9bad241c 100644 --- a/app/database/schemas.py +++ b/app/database/schemas.py @@ -1,28 +1,19 @@ -from datetime import datetime from typing import Optional, Union -from pydantic import BaseModel, EmailError, EmailStr, Field, validator +from pydantic import BaseModel, EmailError, EmailStr, validator EMPTY_FIELD_STRING = "field is required" MIN_FIELD_LENGTH = 3 MAX_FIELD_LENGTH = 20 -def fields_not_empty(field: Optional[str]) -> str: +def fields_not_empty(field: Optional[str]) -> Union[ValueError, str]: """Global function to validate fields are not empty.""" if not field: raise ValueError(EMPTY_FIELD_STRING) return field -class UserModel(BaseModel): - username: str - password: str - email: str = Field(regex="^\\S+@\\S+\\.\\S+$") - language: str - language_id: int - - class UserBase(BaseModel): """ Validating fields types @@ -69,30 +60,32 @@ class UserCreate(UserBase): ) @validator("confirm_password") - def passwords_match(cls, confirm_password: str, values: UserBase) -> str: + def passwords_match( + cls, + confirm_password: str, + values: UserBase, + ) -> Union[ValueError, str]: """Validating passwords fields identical.""" if "password" in values and confirm_password != values["password"]: raise ValueError("doesn't match to password") return confirm_password @validator("username") - def username_length(cls, username: str) -> str: + def username_length(cls, username: str) -> Union[ValueError, str]: """Validating username length is legal""" if not (MIN_FIELD_LENGTH < len(username) < MAX_FIELD_LENGTH): raise ValueError("must contain between 3 to 20 charactars") - if username.startswith("@"): - raise ValueError("username can not start with '@'") return username @validator("password") - def password_length(cls, password: str) -> str: + def password_length(cls, password: str) -> Union[ValueError, str]: """Validating username length is legal""" if not (MIN_FIELD_LENGTH < len(password) < MAX_FIELD_LENGTH): raise ValueError("must contain between 3 to 20 charactars") return password @validator("email") - def confirm_mail(cls, email: str) -> str: + def confirm_mail(cls, email: str) -> Union[ValueError, str]: """Validating email is valid mail address.""" try: EmailStr.validate(email) @@ -109,23 +102,3 @@ class User(UserBase): id: int is_active: bool - - -class NoteSchema(BaseModel): - title: str - description: Optional[str] = None - timestamp: Optional[datetime] - creator: Optional[User] - - class Config: - orm_mode = True - schema_extra = { - "example": { - "title": "Foo", - "description": "Bar", - }, - } - - -class NoteDB(NoteSchema): - id: int diff --git a/app/dependencies.py b/app/dependencies.py index 01cdcf56..9c28232c 100644 --- a/app/dependencies.py +++ b/app/dependencies.py @@ -17,7 +17,6 @@ templates = Jinja2Templates(directory=TEMPLATES_PATH) templates.env.add_extension("jinja2.ext.i18n") - # Configure logger logger = LoggerCustomizer.make_logger( config.LOG_PATH, diff --git a/app/internal/agenda_events.py b/app/internal/agenda_events.py index b77e2a0b..ccd4bb23 100644 --- a/app/internal/agenda_events.py +++ b/app/internal/agenda_events.py @@ -6,15 +6,15 @@ from sqlalchemy.orm import Session from app.database.models import Event -from app.internal.user import user from app.routers.event import sort_by_date +from app.routers.user import get_all_user_events def get_events_per_dates( - session: Session, - user_id: int, - start: Optional[date], - end: Optional[date], + session: Session, + user_id: int, + start: Optional[date], + end: Optional[date] ) -> Union[Iterator[Event], list]: """Read from the db. Return a list of all the user events between the relevant dates.""" @@ -22,10 +22,14 @@ def get_events_per_dates( if start > end: return [] - return filter_dates( - sort_by_date(user.get_all_user_events(session, user_id)), - start, - end, + return ( + filter_dates( + sort_by_date( + get_all_user_events(session, user_id) + ), + start, + end, + ) ) @@ -50,17 +54,15 @@ def get_time_delta_string(start: date, end: date) -> str: diff = end - start granularity = build_arrow_delta_granularity(diff) duration_string = arrow_end.humanize( - arrow_start, - only_distance=True, - granularity=granularity, + arrow_start, only_distance=True, granularity=granularity ) return duration_string def filter_dates( - events: List[Event], - start: Union[None, date] = None, - end: Union[None, date] = None, + events: List[Event], + start: Union[None, date] = None, + end: Union[None, date] = None, ) -> Iterator[Event]: """Returns all events in a time frame. @@ -80,25 +82,24 @@ def filter_dates( def get_events_in_time_frame( - start_date: Union[date, None], - end_date: Union[date, None], - user_id: int, - db: Session, + start_date: Union[date, None], + end_date: Union[date, None], + user_id: int, db: Session ) -> Iterator[Event]: """Yields all user's events in a time frame.""" - events = user.get_all_user_events(db, user_id) + events = get_all_user_events(db, user_id) yield from filter_dates(events, start_date, end_date) -def get_events_for_the_week( - db: Session, - user_id: int, -) -> Tuple[Union[Iterator[Event], list], Dict]: +def get_events_for_the_week(db: Session, user_id: int + ) -> Tuple[Union[Iterator[Event], list], Dict]: WEEK_DAYS = 7 start_date = date.today() end_date = start_date + timedelta(days=WEEK_DAYS - 1) - events_this_week = get_events_per_dates(db, user_id, start_date, end_date) + events_this_week = get_events_per_dates( + db, user_id, start_date, end_date + ) events_for_graph = { str(start_date + timedelta(i)): 0 for i in range(WEEK_DAYS) } diff --git a/app/internal/email.py b/app/internal/email.py index d75e1e09..87092f7f 100644 --- a/app/internal/email.py +++ b/app/internal/email.py @@ -7,26 +7,16 @@ from pydantic.errors import EmailError from sqlalchemy.orm.session import Session -from app.config import ( - CALENDAR_HOME_PAGE, - CALENDAR_REGISTRATION_PAGE, - CALENDAR_SITE_NAME, - DOMAIN, - email_conf, -) +from app.config import (CALENDAR_HOME_PAGE, CALENDAR_REGISTRATION_PAGE, + CALENDAR_SITE_NAME, email_conf, templates) from app.database.models import Event, User -from app.dependencies import templates -from app.internal.security.schema import ForgotPassword mail = FastMail(email_conf) def send( - session: Session, - event_used: int, - user_to_send: int, - title: str, - background_tasks: BackgroundTasks = BackgroundTasks, + session: Session, event_used: int, user_to_send: int, + title: str, background_tasks: BackgroundTasks = BackgroundTasks ) -> bool: """This function is being used to send emails in the background. It takes an event and a user and it sends the event to the user. @@ -42,8 +32,10 @@ def send( Returns: bool: Returns True if the email was sent, else returns False. """ - event_used = session.query(Event).filter(Event.id == event_used).first() - user_to_send = session.query(User).filter(User.id == user_to_send).first() + event_used = session.query(Event).filter( + Event.id == event_used).first() + user_to_send = session.query(User).filter( + User.id == user_to_send).first() if not user_to_send or not event_used: return False if not verify_email_pattern(user_to_send.email): @@ -53,21 +45,18 @@ def send( recipients = {"email": [user_to_send.email]}.get("email") body = f"begins at:{event_used.start} : {event_used.content}" - background_tasks.add_task( - send_internal, - subject=subject, - recipients=recipients, - body=body, - ) + background_tasks.add_task(send_internal, + subject=subject, + recipients=recipients, + body=body) return True -def send_email_invitation( - sender_name: str, - recipient_name: str, - recipient_mail: str, - background_tasks: BackgroundTasks = BackgroundTasks, -) -> bool: +def send_email_invitation(sender_name: str, + recipient_name: str, + recipient_mail: str, + background_tasks: BackgroundTasks = BackgroundTasks + ) -> bool: """ This function takes as parameters the sender's name, the recipient's name and his email address, configuration, and @@ -92,35 +81,28 @@ def send_email_invitation( return False template = templates.get_template("invite_mail.html") - html = template.render( - recipient=recipient_name, - sender=sender_name, - site_name=CALENDAR_SITE_NAME, - registration_link=CALENDAR_REGISTRATION_PAGE, - home_link=CALENDAR_HOME_PAGE, - addr_to=recipient_mail, - ) + html = template.render(recipient=recipient_name, sender=sender_name, + site_name=CALENDAR_SITE_NAME, + registration_link=CALENDAR_REGISTRATION_PAGE, + home_link=CALENDAR_HOME_PAGE, + addr_to=recipient_mail) subject = "Invitation" recipients = [recipient_mail] body = html subtype = "html" - background_tasks.add_task( - send_internal, - subject=subject, - recipients=recipients, - body=body, - subtype=subtype, - ) + background_tasks.add_task(send_internal, + subject=subject, + recipients=recipients, + body=body, + subtype=subtype) return True -def send_email_file( - file_path: str, - recipient_mail: str, - background_tasks: BackgroundTasks = BackgroundTasks, -): +def send_email_file(file_path: str, + recipient_mail: str, + background_tasks: BackgroundTasks = BackgroundTasks): """ his function takes as parameters the file's path, the recipient's email address, configuration, and @@ -144,23 +126,19 @@ def send_email_file( body = "file" file_attachments = [file_path] - background_tasks.add_task( - send_internal, - subject=subject, - recipients=recipients, - body=body, - file_attachments=file_attachments, - ) + background_tasks.add_task(send_internal, + subject=subject, + recipients=recipients, + body=body, + file_attachments=file_attachments) return True -async def send_internal( - subject: str, - recipients: List[str], - body: str, - subtype: Optional[str] = None, - file_attachments: Optional[List[str]] = None, -): +async def send_internal(subject: str, + recipients: List[str], + body: str, + subtype: Optional[str] = None, + file_attachments: Optional[List[str]] = None): if file_attachments is None: file_attachments = [] @@ -169,10 +147,8 @@ async def send_internal( recipients=[EmailStr(recipient) for recipient in recipients], body=body, subtype=subtype, - attachments=[ - UploadFile(file_attachment) for file_attachment in file_attachments - ], - ) + attachments=[UploadFile(file_attachment) + for file_attachment in file_attachments]) return await send_internal_internal(message) @@ -201,32 +177,3 @@ def verify_email_pattern(email: str) -> bool: return True except EmailError: return False - - -async def send_reset_password_mail( - user: ForgotPassword, - background_tasks: BackgroundTasks, -) -> bool: - """ - This function sends a reset password email to user. - :param user: ForgotPassword schema. - Contains user's email address, jwt verifying token. - :param background_tasks: (BackgroundTasks): Function from fastapi that lets - you apply tasks in the background. - returns True - """ - params = f"?email_verification_token={user.email_verification_token}" - template = templates.get_template("reset_password_mail.html") - html = template.render( - recipient=user.username.lstrip("@"), - link=f"{DOMAIN}/reset-password{params}", - email=user.email, - ) - background_tasks.add_task( - send_internal, - subject="Calendar reset password", - recipients=[user.email], - body=html, - subtype="html", - ) - return True diff --git a/app/internal/event.py b/app/internal/event.py index 3d4bebfe..962f48fb 100644 --- a/app/internal/event.py +++ b/app/internal/event.py @@ -1,4 +1,3 @@ -import functools import logging import re from typing import List, NamedTuple, Set, Union @@ -12,8 +11,7 @@ from sqlalchemy.orm import Session from starlette.status import HTTP_400_BAD_REQUEST -from app.database.models import Country, Event -from app.resources.countries import countries +from app.database.models import Event ZOOM_REGEX = re.compile(r"https://.*?\.zoom.us/[a-z]/.[^.,\b\s]+") @@ -116,43 +114,6 @@ def get_messages( return messages -def add_countries_to_db(session: Session) -> None: - """ - Adding all new countries to the "Country" table in the database. - Information is based on the "countries" list. - (The list is located in app/resources/countries.py) - Names are described either as: - "Country Name, City Name" or - "Country Name" solely. - Timezones are described as "Continent/ City Name" - for example: - name: Israel, Jerusalem - timezone: Asia/Jerusalem - """ - for country in countries: - partial_name = country["name"] - for capital_city in country["timezones"]: - capital_city_name = capital_city.split("/")[-1] - if partial_name != capital_city_name: - name = partial_name + ", " + capital_city_name - else: - name = capital_city_name - new_country = Country(name=name, timezone=str(capital_city)) - session.merge(new_country) - session.commit() - - -@functools.lru_cache(maxsize=None) -def get_all_countries_names(session: Session) -> List[str]: - """ - Returns a cached list of the countries names. - """ - db_entity = session.query(Country).first() - if not db_entity: - add_countries_to_db(session=session) - return session.query(Country.name).all() - - async def get_location_coordinates( address: str, ) -> Union[Location, str]: diff --git a/app/internal/features.py b/app/internal/features.py deleted file mode 100644 index 4ef8628e..00000000 --- a/app/internal/features.py +++ /dev/null @@ -1,183 +0,0 @@ -from functools import wraps -from typing import Dict, List - -from fastapi import Depends, Request -from sqlalchemy.orm import Session -from sqlalchemy.sql import exists -from starlette.responses import RedirectResponse - -from app.database.models import Feature, UserFeature -from app.dependencies import SessionLocal, get_db -from app.internal.features_index import features -from app.internal.security.dependencies import current_user -from app.internal.security.ouath2 import get_authorization_cookie -from app.internal.utils import create_model - - -def feature_access_filter(call_next): - @wraps(call_next) - async def wrapper(*args, **kwargs): - request = kwargs["request"] - - if request.headers["user-agent"] == "testclient": - # in case it's a unit test. - return await call_next(*args, **kwargs) - - # getting the url route path for matching with the database. - route = "/" + str(request.url).replace(str(request.base_url), "") - - # getting access status. - access = await is_access_allowd(route=route, request=request) - - if access: - # in case the feature is enabled or access is allowed. - return await call_next(*args, **kwargs) - - elif "referer" not in request.headers: - # in case request come straight from address bar in browser. - return RedirectResponse(url="/") - - # in case the feature is disabled or access isn't allowed. - return RedirectResponse(url=request.headers["referer"]) - - return wrapper - - -def create_features_at_startup(session: Session) -> bool: - for feat in features: - if not is_feature_exists(feature=feat, session=session): - create_feature(**feat, db=session) - return True - - -def is_user_has_feature( - session: Session, - feature_id: int, - user_id: int, -) -> bool: - return session.query( - exists() - .where(UserFeature.user_id == user_id) - .where(UserFeature.feature_id == feature_id), - ).scalar() - - -def delete_feature( - feature: Feature, - session: Session = Depends(get_db), -) -> None: - session.query(UserFeature).filter_by(feature_id=feature.id).delete() - session.query(Feature).filter_by(id=feature.id).delete() - session.commit() - - -def is_feature_exists(feature: Dict[str, str], session: Session) -> bool: - is_exists = session.query( - exists() - .where(Feature.name == feature["name"]) - .where(Feature.route == feature["route"]), - ).scalar() - - return is_exists - - -def update_feature( - feature: Feature, - feature_dict: Dict[str, str], - session: Session = Depends(get_db), -) -> Feature: - feature.name = feature_dict["name"] - feature.route = feature_dict["route"] - feature.description = feature_dict["description"] - feature.creator = feature_dict["creator"] - session.commit() - return feature - - -async def is_access_allowd(request: Request, route: str) -> bool: - session = SessionLocal() - - # Get current user. - # Note: can't use dependency beacause its designed for routes only. - # current_user return schema not an db model. - jwt = await get_authorization_cookie(request=request) - user = await current_user(request=request, jwt=jwt, db=session) - - feature = session.query(Feature).filter_by(route=route).first() - - if feature is None: - # in case there is no feature exists in the database that match the - # route that gived by to the request. - return True - - user_feature = session.query( - exists().where( - (UserFeature.feature_id == feature.id) - & (UserFeature.user_id == user.user_id), - ), - ).scalar() - - return user_feature - - -def create_feature( - db: Session, - name: str, - route: str, - description: str, - creator: str = None, -) -> Feature: - """Creates a feature.""" - return create_model( - db, - Feature, - name=name, - route=route, - creator=creator, - description=description, - ) - - -def create_user_feature_association( - db: Session, - feature_id: int, - user_id: int, - is_enable: bool, -) -> UserFeature: - """Creates an association.""" - return create_model( - db, - UserFeature, - user_id=user_id, - feature_id=feature_id, - is_enable=is_enable, - ) - - -def get_user_installed_features( - user_id: int, - session: Session = Depends(get_db), -) -> List[Feature]: - return ( - session.query(Feature) - .join(UserFeature) - .filter(UserFeature.user_id == user_id) - .all() - ) - - -def get_user_uninstalled_features( - user_id: int, - session: Session = Depends(get_db), -) -> List[Feature]: - return ( - session.query(Feature) - .filter( - Feature.id.notin_( - session.query(UserFeature.feature_id).filter( - UserFeature.user_id == user_id, - ), - ), - ) - .all() - ) diff --git a/app/internal/features_index.py b/app/internal/features_index.py deleted file mode 100644 index 89743a2e..00000000 --- a/app/internal/features_index.py +++ /dev/null @@ -1,51 +0,0 @@ -''' - This file purpose is for developers to add their features to the database - in one convenient place, every time the system loads up it's adding and - updating the features in the features table in the database. - - To update a feature, The developer needs to change the name or the route - and let the system load, but not change both at the same time otherwise - it will create junk and unnecessary duplicates. - - * IMPORTANT - To enable features panel functionlity the developer must * - * add the feature_access_filter decorator to ALL the feature routes * - * Please see the example below. * - - Enjoy and good luck :) -''' - -''' -Example to feature stracture: - -{ - "name": "", - "route": "/", - "description": "", - "creator": "" -} -''' - -''' -* IMPORTANT * - -Example to decorator placement: - - @router.get("/") - @feature_access_filter <---- just above def keyword! - def my_cool_feature_route(): - .... - ... - some code. - .. - . - -''' - -features = [ - { - "name": "Google Sync", - "route": "/google/sync", - "description": "Sync Google Calendar events with Pylender", - "creator": "Liran Caduri" - }, -] diff --git a/app/internal/friend_view.py b/app/internal/friend_view.py index 46cd011d..85e8190f 100644 --- a/app/internal/friend_view.py +++ b/app/internal/friend_view.py @@ -3,22 +3,22 @@ from sqlalchemy.orm import Session from app.database.models import Event -from app.internal.user import user from app.routers.event import sort_by_date +from app.routers.user import get_all_user_events def get_events_per_friend( - session: Session, - user_id: int, - my_friend: str, + session: Session, + user_id: int, + my_friend: str, ) -> List[Event]: - """My_friend is the name of a person that appears in the invite list of + """ My_friend is the name of a person that appears in the invite list of events. He is not necessarily a registered userץ The variable is used to show all events where we are both in the invitees list""" events_together = [] - sorted_events = sort_by_date(user.get_all_user_events(session, user_id)) + sorted_events = sort_by_date(get_all_user_events(session, user_id)) for event in sorted_events: - if my_friend in event.invitees.split(","): + if my_friend in event.invitees.split(','): events_together.append(event) return events_together diff --git a/app/internal/notes/notes.py b/app/internal/notes/notes.py deleted file mode 100644 index 5a8e999f..00000000 --- a/app/internal/notes/notes.py +++ /dev/null @@ -1,69 +0,0 @@ -from datetime import datetime -from typing import Any, Dict, List - -from fastapi import HTTPException, status -from sqlalchemy.orm import Session - -from app.database.models import Note -from app.database.schemas import NoteSchema - - -async def create(session: Session, payload: NoteSchema) -> int: - note = Note( - title=payload.title, - description=payload.description, - creator=payload.creator, - ) - session.add(note) - session.commit() - session.refresh(note) - return note.id - - -async def view(session: Session, note_id: int) -> Note: - note = session.query(Note).filter_by(id=note_id).first() - if not note: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Note with id {note_id} not found", - ) - return note - - -async def get_all( - session: Session, - skip: int = 0, - limit: int = 100, -) -> List[Note]: - return session.query(Note).offset(skip).limit(limit).all() - - -async def update(request: NoteSchema, session: Session, note_id: int) -> str: - note = session.query(Note).filter_by(id=note_id) - if not note.first(): - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Note with id {note_id} not found", - ) - if request.timestamp is None: - request.timestamp = datetime.utcnow() - note.update(request.dict(exclude_unset=True), synchronize_session=False) - session.commit() - return "updated" - - -async def delete(session: Session, note_id: int) -> str: - note = session.query(Note).filter_by(id=note_id) - if not note.first(): - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Note with id {note_id} not found", - ) - note.delete(synchronize_session=False) - session.commit() - return "deleted" - - -async def create_note(note: NoteSchema, session: Session) -> Dict[str, Any]: - note_id = await create(session, note) - return {"id": note_id, **dict(note)} diff --git a/app/internal/security/dependencies.py b/app/internal/security/dependencies.py index 19db881b..e42e4c0e 100644 --- a/app/internal/security/dependencies.py +++ b/app/internal/security/dependencies.py @@ -1,5 +1,3 @@ -from typing import Optional - from fastapi import Depends, HTTPException from starlette.requests import Request from starlette.status import HTTP_401_UNAUTHORIZED @@ -22,13 +20,7 @@ async def is_logged_in( """ A dependency function protecting routes for only logged in user """ - jwt_payload = get_jwt_token(jwt) - user_id = jwt_payload.get("user_id") - if not user_id: - raise HTTPException( - status_code=HTTP_401_UNAUTHORIZED, - detail="Your token is not valid. Please log in again", - ) + await get_jwt_token(db, jwt) return True @@ -40,9 +32,8 @@ async def is_manager( """ A dependency function protecting routes for only logged in manager """ - jwt_payload = get_jwt_token(jwt) - user_id = jwt_payload.get("user_id") - if jwt_payload.get("is_manager") and user_id: + jwt_payload = await get_jwt_token(db, jwt) + if jwt_payload.get("is_manager"): return True raise HTTPException( status_code=HTTP_401_UNAUTHORIZED, @@ -60,7 +51,7 @@ async def current_user_from_db( Returns logged in User object. A dependency function protecting routes for only logged in user. """ - jwt_payload = get_jwt_token(jwt) + jwt_payload = await get_jwt_token(db, jwt) username = jwt_payload.get("sub") user_id = jwt_payload.get("user_id") db_user = await User.get_by_username(db, username=username) @@ -83,30 +74,7 @@ async def current_user( Returns logged in User object. A dependency function protecting routes for only logged in user. """ - jwt_payload = get_jwt_token(jwt) - username = jwt_payload.get("sub") - user_id = jwt_payload.get("user_id") - if not user_id: - raise HTTPException( - status_code=HTTP_401_UNAUTHORIZED, - detail="Your token is not valid. Please log in again", - ) - return schema.CurrentUser(user_id=user_id, username=username) - - -def get_jinja_current_user(request: Request) -> Optional[schema.CurrentUser]: - """Return the currently logged in user. - Returns logged in User object if exists, None if not. - Set as a jinja global parameter. - """ - if "Authorization" not in request.cookies: - return None - jwt_payload = get_jwt_token(request.cookies["Authorization"]) + jwt_payload = await get_jwt_token(db, jwt) username = jwt_payload.get("sub") user_id = jwt_payload.get("user_id") - if not user_id: - raise HTTPException( - status_code=HTTP_401_UNAUTHORIZED, - detail="Your token is not valid. Please log in again", - ) return schema.CurrentUser(user_id=user_id, username=username) diff --git a/app/internal/security/ouath2.py b/app/internal/security/ouath2.py index a20f3b0e..6f99ae48 100644 --- a/app/internal/security/ouath2.py +++ b/app/internal/security/ouath2.py @@ -9,7 +9,7 @@ from sqlalchemy.orm import Session from starlette.requests import Request from starlette.responses import RedirectResponse -from starlette.status import HTTP_302_FOUND, HTTP_401_UNAUTHORIZED +from starlette.status import HTTP_401_UNAUTHORIZED from app.config import JWT_ALGORITHM, JWT_KEY, JWT_MIN_EXP from app.database.models import User @@ -20,20 +20,7 @@ oauth_schema = OAuth2PasswordBearer(tokenUrl="/login") -async def update_password( - db: Session, - username: str, - user_password: str, -) -> None: - """Updating User password in database""" - db_user = await User.get_by_username(db=db, username=username) - hashed_password = get_hashed_password(user_password) - db_user.password = hashed_password - db.commit() - return - - -def get_hashed_password(password: str) -> str: +def get_hashed_password(password: bytes) -> str: """Hashing user password""" return pwd_context.hash(password) @@ -43,47 +30,17 @@ def verify_password(plain_password: str, hashed_password: str) -> bool: return pwd_context.verify(plain_password, hashed_password) -async def is_email_compatible_to_username( - db: Session, - user: schema.ForgotPassword, - email: bool = False, -) -> Union[schema.ForgotPassword, bool]: - """ - Verifying database record by username. - Comparing given email to database record, - """ - db_user = await User.get_by_username( - db=db, - username=user.username.lstrip("@"), - ) - if not db_user: - return False - if db_user.email == user.email: - return schema.ForgotPassword( - username=user.username, - user_id=db_user.id, - email=db_user.email, - ) - return False - - async def authenticate_user( db: Session, - user: schema.LoginUser, + new_user: schema.LoginUser, ) -> Union[schema.LoginUser, bool]: - """ - Verifying database record by username. - Comparing given password to database record, - varies with which function called this action. - """ - db_user = await User.get_by_username(db=db, username=user.username) - if not db_user: - return False - elif verify_password(user.password, db_user.password): + """Verifying user is in database and password is correct""" + db_user = await User.get_by_username(db=db, username=new_user.username) + if db_user and verify_password(new_user.password, db_user.password): return schema.LoginUser( user_id=db_user.id, is_manager=db_user.is_manager, - username=user.username, + username=new_user.username, password=db_user.password, ) return False @@ -106,7 +63,8 @@ def create_jwt_token( return jwt_token -def get_jwt_token( +async def get_jwt_token( + db: Session, token: str = Depends(oauth_schema), path: Union[bool, str] = None, ) -> User: @@ -163,6 +121,6 @@ async def auth_exception_handler( """ paramas = f"?next={exc.headers}&message={exc.detail}" url = f"/login{paramas}" - response = RedirectResponse(url=url, status_code=HTTP_302_FOUND) + response = RedirectResponse(url=url) response.delete_cookie("Authorization") return response diff --git a/app/internal/security/schema.py b/app/internal/security/schema.py index 2e5fee8d..95645ac0 100644 --- a/app/internal/security/schema.py +++ b/app/internal/security/schema.py @@ -1,6 +1,6 @@ -from typing import Optional, Union +from typing import Optional -from pydantic import BaseModel, validator +from pydantic import BaseModel class CurrentUser(BaseModel): @@ -8,7 +8,6 @@ class CurrentUser(BaseModel): Validating fields types Returns a user details as a class. """ - user_id: Optional[int] username: str @@ -21,65 +20,5 @@ class LoginUser(CurrentUser): Validating fields types Returns a User object for signing in. """ - is_manager: Optional[bool] password: str - - -class ForgotPassword(BaseModel): - """ - BaseModel for collecting and verifying user - details sending a token via email - """ - - username: str - email: str - user_id: Optional[str] = None - email_verification_token: Optional[str] = None - is_manager: Optional[bool] = False - - class Config: - orm_mode = True - - @validator("username") - def password_length(cls, username: str) -> Union[ValueError, str]: - """Validating username length is legal""" - if not (MIN_FIELD_LENGTH < len(username) < MAX_FIELD_LENGTH): - raise ValueError - return username - - -MIN_FIELD_LENGTH = 3 -MAX_FIELD_LENGTH = 20 - - -class ResetPassword(BaseModel): - """ - Validating fields types - """ - - username: str - password: str - confirm_password: str - - class Config: - orm_mode = True - fields = {"confirm_password": "confirm-password"} - - @validator("confirm_password") - def passwords_match( - cls, - confirm_password: str, - values: BaseModel, - ) -> Union[ValueError, str]: - """Validating passwords fields identical.""" - if "password" in values and confirm_password != values["password"]: - raise ValueError - return confirm_password - - @validator("password") - def password_length(cls, password: str) -> Union[ValueError, str]: - """Validating password length is legal""" - if not (MIN_FIELD_LENGTH < len(password) < MAX_FIELD_LENGTH): - raise ValueError - return password diff --git a/app/internal/statistics.py b/app/internal/statistics.py index cc71a343..3cbe63e6 100644 --- a/app/internal/statistics.py +++ b/app/internal/statistics.py @@ -6,7 +6,7 @@ from sqlalchemy.util import symbol from app.database.models import Event, UserEvent -from app.internal.user import user +from app.routers.user import does_user_exist, get_users SUCCESS_STATUS = True ERROR_STATUS = False @@ -42,10 +42,10 @@ def validate_input( - db: Session, - userid: int, - start: datetime.datetime = None, - end: datetime.datetime = None, + db: Session, + userid: int, + start: datetime.datetime = None, + end: datetime.datetime = None, ) -> ValidationResult: """1. input validations: valid userid. @@ -67,7 +67,7 @@ def validate_input( start: start of date range. end: end of date range. """ - if not user.does_user_exist(session=db, user_id=userid): + if not does_user_exist(session=db, user_id=userid): return ValidationResult( valid_input=False, error_text=INVALID_USER, @@ -87,16 +87,12 @@ def validate_input( end=end, ) return ValidationResult( - valid_input=True, - error_text="", - start=start, - end=end, + valid_input=True, error_text="", start=start, end=end ) def get_date_filter_between_dates( - start: datetime.datetime, - end: datetime.datetime, + start: datetime.datetime, end: datetime.datetime ) -> symbol: """ Prepare the filter by dates using declarative SQLAlchemy. @@ -113,10 +109,10 @@ def get_date_filter_between_dates( def get_events_count_stats( - db: Session, - userid: int, - start: datetime.datetime, - end: datetime.datetime, + db: Session, + userid: int, + start: datetime.datetime, + end: datetime.datetime, ) -> Dict[str, Dict[str, int]]: """calculate statistics and events relevant for the requested user and the requested date range. @@ -137,14 +133,16 @@ def get_events_count_stats( by_user_id = UserEvent.user_id == userid by_owner_id = Event.owner_id == userid meetings_for_user = ( - db.query(Event.id) + db + .query(Event.id) .join(user_to_event) .filter(by_user_id) .filter(get_date_filter_between_dates(start, end)) .count() ) created_by_user = ( - db.query(Event.id) + db + .query(Event.id) .filter(by_owner_id) .filter(get_date_filter_between_dates(start, end)) .count() @@ -153,15 +151,15 @@ def get_events_count_stats( "events_count_stats": { "meetings_for_user": meetings_for_user, "created_by_user": created_by_user, - }, + } } def get_events_by_date( - db: Session, - userid: int, - start: datetime.datetime, - end: datetime.datetime, + db: Session, + userid: int, + start: datetime.datetime, + end: datetime.datetime, ) -> List[Tuple[datetime.datetime, int]]: """get date + number of events on it @@ -179,7 +177,8 @@ def get_events_by_date( by_user_id = UserEvent.user_id == userid user_to_event = (UserEvent, UserEvent.event_id == Event.id) return ( - db.query(start_date, events_count) + db + .query(start_date, events_count) .join(user_to_event) .filter(by_user_id) .filter(get_date_filter_between_dates(start, end)) @@ -211,21 +210,18 @@ def calc_daily_events_statistics( if num_of_days_in_period > len(events_by_date): min_events_in_day = 0 avg_events_per_day = round( - sum_events_per_period / num_of_days_in_period, - 2, + sum_events_per_period / num_of_days_in_period, 2 ) return DailyEventsStatistics( - min_events_in_day, - max_events_in_day, - avg_events_per_day, + min_events_in_day, max_events_in_day, avg_events_per_day ) def get_daily_events_statistics( - db: Session, - userid: int, - start: datetime.datetime, - end: datetime.datetime, + db: Session, + userid: int, + start: datetime.datetime, + end: datetime.datetime, ) -> Dict[str, Dict[str, int]]: """calculate statistics for daily events relevant for the requested user and the requested date range. logic is performed in: @@ -246,24 +242,22 @@ def get_daily_events_statistics( """ events_by_date = get_events_by_date(db, userid, start, end) daily_events_statistics = calc_daily_events_statistics( - events_by_date, - start, - end, + events_by_date, start, end ) return { "day_events_stats": { "min_events_in_day": daily_events_statistics.min_events_in_day, "max_events_in_day": daily_events_statistics.max_events_in_day, "avg_events_per_day": daily_events_statistics.avg_events_per_day, - }, + } } def get_events_duration_statistics_from_db( - db: Session, - userid: int, - start: datetime.datetime, - end: datetime.datetime, + db: Session, + userid: int, + start: datetime.datetime, + end: datetime.datetime, ) -> EventsDurationStatistics: """get data of shortest, longest and average event duration from the db @@ -280,7 +274,8 @@ def get_events_duration_statistics_from_db( user_to_event = (UserEvent, UserEvent.event_id == Event.id) by_user_id = UserEvent.user_id == userid events_duration_statistics = ( - db.query( + db + .query( (func.min(event_duration) * NIN_IN_DAY), (func.max(event_duration) * NIN_IN_DAY), (func.avg(event_duration) * NIN_IN_DAY), @@ -297,17 +292,15 @@ def get_events_duration_statistics_from_db( average_event=round(events_duration_statistics[0][2]), ) return EventsDurationStatistics( - shortest_event=0, - longest_event=0, - average_event=0, + shortest_event=0, longest_event=0, average_event=0 ) def get_events_duration_statistics( - db: Session, - userid: int, - start: datetime.datetime, - end: datetime.datetime, + db: Session, + userid: int, + start: datetime.datetime, + end: datetime.datetime, ) -> Dict[str, Dict[str, float]]: """calculate statistics for events durations relevant for the requested user and the requested date range. @@ -326,25 +319,22 @@ def get_events_duration_statistics( average_event: average event the user has. """ events_duration_statistics = get_events_duration_statistics_from_db( - db, - userid, - start, - end, + db, userid, start, end, ) return { "events_duration_statistics": { "shortest_event": events_duration_statistics.shortest_event, "longest_event": events_duration_statistics.longest_event, "average_event": events_duration_statistics.average_event, - }, + } } def get_participants_statistics( - db: Session, - userid: int, - start: datetime.datetime, - end: datetime.datetime, + db: Session, + userid: int, + start: datetime.datetime, + end: datetime.datetime, ) -> Dict[str, Dict[str, Union[str, int]]]: """calculate statistics for events participants relevant for the requested user and the requested date range. @@ -367,14 +357,16 @@ def get_participants_statistics( user_to_event = (UserEvent, UserEvent.event_id == Event.id) participant_count = func.count(UserEvent.user_id) subquery = ( - db.query(Event.id) + db + .query(Event.id) .join(user_to_event) .filter(by_user_id) .filter(get_date_filter_between_dates(start, end)) .subquery() ) event_participants = ( - db.query(UserEvent.user_id, participant_count) + db + .query(UserEvent.user_id, participant_count) .filter(by_not_user_id) .filter(UserEvent.event_id.in_(subquery)) .group_by(UserEvent.user_id) @@ -385,14 +377,13 @@ def get_participants_statistics( return { "participants_statistics": { "max_events": event_participants[1], - "participant_name": user.get_users( - db, - id=event_participants[0], + "participant_name": get_users( + db, id=event_participants[0] )[0].username, - }, + } } return { - "participants_statistics": {"max_events": 0, "participant_name": ""}, + "participants_statistics": {"max_events": 0, "participant_name": ""} } @@ -435,7 +426,7 @@ def prepare_display_text( "stat_4": f"Max events with the same person (" f"{output['participants_statistics']['participant_name']}) " f"is {output['participants_statistics']['max_events']}", - }, + } } return display_text @@ -464,13 +455,11 @@ def update_output(output, *params) -> Dict[str, Union[str, bool]]: ] for call_function in call_functions: output.update(call_function(*params)) - output.update( - prepare_display_text( - output, - start=params[2], - end=params[3], - ), - ) + output.update(prepare_display_text( + output, + start=params[2], + end=params[3], + )) return output diff --git a/app/internal/todo_list.py b/app/internal/todo_list.py deleted file mode 100644 index 2de257aa..00000000 --- a/app/internal/todo_list.py +++ /dev/null @@ -1,35 +0,0 @@ -from datetime import date, time - -from sqlalchemy.orm import Session - -from app.database.models import Task -from app.internal.utils import create_model - - -def create_task( - db: Session, - title: str, - description: str, - date_str: date, - time_str: time, - owner_id: int, - is_important: bool, -) -> Task: - """Creates and saves a new task.""" - task = create_model( - db, - Task, - title=title, - description=description, - date=date_str, - time=time_str, - owner_id=owner_id, - is_important=is_important, - is_done=False, - ) - return task - - -def by_id(db: Session, task_id: int) -> Task: - task = db.query(Task).filter_by(id=task_id).one() - return task diff --git a/app/internal/translation.py b/app/internal/translation.py index 37f4ee3c..4fd0510e 100644 --- a/app/internal/translation.py +++ b/app/internal/translation.py @@ -9,7 +9,7 @@ from textblob.exceptions import NotTranslated from app.database.models import Language -from app.internal.user.user import get_users +from app.routers.user import get_users download_corpora.download_all() diff --git a/app/internal/user/__init__.py b/app/internal/user/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/app/internal/user/user.py b/app/internal/user/user.py deleted file mode 100644 index 13f2a09e..00000000 --- a/app/internal/user/user.py +++ /dev/null @@ -1,132 +0,0 @@ -from typing import List - -from sqlalchemy.exc import SQLAlchemyError -from sqlalchemy.orm import Session - -from app.database import models, schemas -from app.internal.security.ouath2 import get_hashed_password -from app.internal.utils import save - - -def get_by_id(db: Session, user_id: int) -> models.User: - """query database for a user by unique id""" - return db.query(models.User).filter(models.User.id == user_id).first() - - -def get_by_username(db: Session, username: str) -> models.User: - """query database for a user by unique username""" - return ( - db.query(models.User).filter(models.User.username == username).first() - ) - - -def get_by_mail(db: Session, email: str) -> models.User: - """query database for a user by unique email""" - return db.query(models.User).filter(models.User.email == email).first() - - -def create(db: Session, user: schemas.UserCreate) -> models.User: - """ - creating a new User object in the database, with hashed password - """ - unhashed_password = user.password.encode("utf-8") - hashed_password = get_hashed_password(unhashed_password) - user_details = { - "username": user.username, - "full_name": user.full_name, - "email": user.email, - "password": hashed_password, - "description": user.description, - } - db_user = models.User(**user_details) - db.add(db_user) - db.commit() - db.refresh(db_user) - return db_user - - -def delete_by_mail(db: Session, email: str) -> None: - """deletes a user from database by unique email""" - db_user = get_by_mail(db=db, email=email) - db.delete(db_user) - db.commit() - - -def get_users(session: Session, **param): - """Returns all users filtered by param.""" - try: - users = list(session.query(models.User).filter_by(**param)) - except SQLAlchemyError: - return [] - else: - return users - - -def does_user_exist( - session: Session, *, user_id=None, username=None, email=None -): - """Returns True if user exists, False otherwise. - function can receive one of the there parameters""" - if user_id: - return len(get_users(session=session, id=user_id)) == 1 - if username: - return len(get_users(session=session, username=username)) == 1 - if email: - return len(get_users(session=session, email=email)) == 1 - return False - - -def get_all_user_events(session: Session, user_id: int) -> List[models.Event]: - """Returns all events that the user participants in.""" - return ( - session.query(models.Event) - .join(models.UserEvent) - .filter(models.UserEvent.user_id == user_id) - .all() - ) - - -def _create_user(session, **kw) -> models.User: - """Creates and saves a new user.""" - user = models.User(**kw) - save(session, user) - return user - - -async def create_user(db: Session, user: schemas.UserCreate) -> models.User: - """ - creating a new User object in the database, with hashed password - """ - unhashed_password = user.password.encode("utf-8") - hashed_password = get_hashed_password(unhashed_password) - user_details = { - "username": user.username, - "full_name": user.full_name, - "email": user.email, - "password": hashed_password, - "description": user.description, - "language_id": user.language_id, - "target_weight": user.target_weight, - } - return _create_user(**user_details, session=db) - - -async def check_unique_fields( - db: Session, - new_user: schemas.UserCreate, -) -> dict: - """Verifying new user details are unique. Return relevant errors""" - errors = {} - if db.query( - db.query(models.User) - .filter(models.User.username == new_user.username) - .exists(), - ).scalar(): - errors["username"] = "That username is already taken" - if db.query( - db.query(models.User) - .filter(models.User.email == new_user.email) - .exists(), - ).scalar(): - errors["email"] = "Email already registered" - return errors diff --git a/app/internal/notes/__init__.py b/app/internal/week.py similarity index 100% rename from app/internal/notes/__init__.py rename to app/internal/week.py diff --git a/app/locales/en/LC_MESSAGES/base.mo b/app/locales/en/LC_MESSAGES/base.mo index a2af11c0..689acb09 100644 Binary files a/app/locales/en/LC_MESSAGES/base.mo and b/app/locales/en/LC_MESSAGES/base.mo differ diff --git a/app/locales/en/LC_MESSAGES/base.po b/app/locales/en/LC_MESSAGES/base.po index 948c8761..15493180 100644 --- a/app/locales/en/LC_MESSAGES/base.po +++ b/app/locales/en/LC_MESSAGES/base.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2021-02-07 21:31+0000\n" +"POT-Creation-Date: 2021-02-25 15:34+0200\n" "PO-Revision-Date: 2021-01-26 21:31+0200\n" "Last-Translator: FULL NAME \n" "Language: en\n" @@ -18,115 +18,638 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.9.0\n" -#: app/routers/profile.py:20 +#: app/internal/astronomy.py:11 +msgid "No response from server." +msgstr "" + +#: app/routers/export.py:18 msgid "Not found" msgstr "" -#: app/templates/agenda.html:11 +#: app/routers/search.py:53 +msgid "Invalid request." +msgstr "" + +#: app/routers/search.py:58 +msgid "No matching results for '{keywords}'." +msgstr "" + +#: app/templates/agenda.html:14 +#: app/templates/partials/user_profile/sidebar_left/features_card/export_calendar.html:9 msgid "From" msgstr "" -#: app/templates/agenda.html:13 -msgid "to" +#: app/templates/agenda.html:18 +#: app/templates/partials/user_profile/sidebar_left/features_card/export_calendar.html:11 +msgid "To" msgstr "" -#: app/templates/agenda.html:15 +#: app/templates/agenda.html:22 msgid "Get Agenda" msgstr "" -#: app/templates/agenda.html:20 +#: app/templates/agenda.html:28 +msgid "Filter events by category" +msgstr "" + +#: app/templates/agenda.html:33 +msgid "Filter by categories" +msgstr "" + +#: app/templates/agenda.html:39 msgid "Today" msgstr "" -#: app/templates/agenda.html:23 -msgid "Next week" +#: app/templates/agenda.html:42 +msgid "Next 7 days" msgstr "" -#: app/templates/agenda.html:26 -msgid "Next month" +#: app/templates/agenda.html:45 +msgid "Next 30 days" msgstr "" -#: app/templates/agenda.html:33 +#: app/templates/agenda.html:49 +msgid "Week graph" +msgstr "" + +#: app/templates/agenda.html:61 msgid "Start date is greater than end date" msgstr "" -#: app/templates/agenda.html:35 +#: app/templates/agenda.html:63 msgid "No events found..." msgstr "" -#: app/templates/profile.html:50 -msgid "Update name" +#: app/templates/agenda.html:77 +#: app/templates/partials/calendar/event/edit_event_details_tab.html:111 +#: app/templates/partials/calendar/event/view_event_details_tab.html:22 +msgid "Busy" +msgstr "" + +#: app/templates/agenda.html:77 +#: app/templates/partials/calendar/event/edit_event_details_tab.html:110 +#: app/templates/partials/calendar/event/view_event_details_tab.html:22 +msgid "Free" +msgstr "" + +#: app/templates/agenda.html:80 +#, python-format +msgid "duration: %(duration)s" +msgstr "" + +#: app/templates/archive.html:2 app/templates/archive.html:5 +msgid "Archived notifications" +msgstr "" + +#: app/templates/archive.html:13 +msgid "You don't have any archived notifications." +msgstr "" + +#: app/templates/base.html:41 +msgid "Sign Out" +msgstr "" + +#: app/templates/base.html:68 +msgid "About Us" +msgstr "" + +#: app/templates/notifications.html:2 +msgid "Notifications" +msgstr "" + +#: app/templates/notifications.html:5 +msgid "New notifications" +msgstr "" + +#: app/templates/notifications.html:13 +msgid "Mark all as read" +msgstr "" + +#: app/templates/notifications.html:23 +msgid "You don't have any new notifications." +msgstr "" + +#: app/templates/on_this_day.html:2 +msgid "On this day" +msgstr "" + +#: app/templates/on_this_day.html:11 +msgid "Explore more..." +msgstr "" + +#: app/templates/partials/base.html:38 +msgid "Pylendar" +msgstr "" + +#: app/templates/partials/calendar/event/comments_tab.html:2 +msgid "Write your comment here..." +msgstr "" + +#: app/templates/partials/calendar/event/comments_tab.html:3 +msgid "Post comment" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:8 +msgid "Voice" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:12 +msgid "Volume" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:17 +msgid "Pitch" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:22 +msgid "Rate" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:29 +msgid "Start date:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:30 +msgid "Start date" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:31 +msgid "Start time:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:32 +msgid "Start time" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:36 +msgid "End date:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:37 +msgid "End date" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:38 +msgid "End time:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:39 +msgid "End time" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:43 +#: app/templates/partials/calendar/event/edit_event_details_tab.html:44 +msgid "Location" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:54 +msgid "Title" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:59 +msgid "Item name" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:61 +msgid "Amount" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:63 +msgid "Participant" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:70 +msgid "Add an item" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:77 +msgid "Invited emails:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:78 +msgid "Invited emails, separated by commas" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:82 +msgid "Image:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:88 +msgid "Description:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:93 +msgid "Color:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:95 +msgid "Red" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:96 +msgid "Green" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:97 +msgid "Blue" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:102 +msgid "All-day:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:104 +#: app/templates/partials/calendar/event/edit_event_details_tab.html:135 +msgid "Yes" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:105 +#: app/templates/partials/calendar/event/edit_event_details_tab.html:136 +msgid "No" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:108 +msgid "Availability:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:114 +msgid "Privacy:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:133 +msgid "Google event:" +msgstr "" + +#: app/templates/partials/calendar/event/view_event_details_tab.html:61 +msgid "Choose user" +msgstr "" + +#: app/templates/partials/calendar/event/view_event_details_tab.html:75 +#: app/templates/partials/calendar/event/view_event_details_tab.html:82 +msgid "ICON" +msgstr "" + +#: app/templates/partials/calendar/event/view_event_details_tab.html:76 +#, python-format +msgid "Shared list: %(title)s" +msgstr "" + +#: app/templates/partials/calendar/event/view_event_details_tab.html:84 +#, python-format +msgid "Item %(index)s: %(name)s" +msgstr "" + +#: app/templates/partials/calendar/event/view_event_details_tab.html:85 +#, python-format +msgid "Amount: %(amount)s" +msgstr "" + +#: app/templates/partials/calendar/event/view_event_details_tab.html:86 +#, python-format +msgid "Participant: %(participant)s" +msgstr "" + +#: app/templates/partials/calendar/event/view_event_details_tab.html:94 +msgid "Duplicate" +msgstr "" + +#: app/templates/partials/calendar/event/view_event_details_tab.html:95 +msgid "Edit" +msgstr "" + +#: app/templates/partials/calendar/feature_settings/example.html:1 +msgid "Feature name" +msgstr "" + +#: app/templates/partials/index/navigation.html:10 +msgid "Home" +msgstr "" + +#: app/templates/partials/index/navigation.html:13 +msgid "Profile" +msgstr "" + +#: app/templates/partials/index/navigation.html:16 +msgid "Sign in" +msgstr "" + +#: app/templates/partials/index/navigation.html:19 +msgid "Sign up" +msgstr "" + +#: app/templates/partials/index/navigation.html:24 +msgid "Agenda" +msgstr "" + +#: app/templates/partials/index/navigation.html:27 +msgid "Search" +msgstr "" + +#: app/templates/partials/index/navigation.html:30 +msgid "Audio settings" +msgstr "" + +#: app/templates/partials/index/navigation.html:33 +msgid "Categories" +msgstr "" + +#: app/templates/partials/index/navigation.html:36 +msgid "About us" +msgstr "" + +#: app/templates/partials/index/navigation.html:39 +msgid "Credits" +msgstr "" + +#: app/templates/partials/index/navigation.html:47 +msgid "Salary" +msgstr "" + +#: app/templates/partials/index/navigation.html:50 +msgid "Medication" msgstr "" -#: app/templates/profile.html:57 app/templates/profile.html:77 -#: app/templates/profile.html:97 app/templates/profile.html:120 -#: app/templates/profile.html:140 +#: app/templates/partials/notification/generate_archive.html:7 +#: app/templates/partials/notification/generate_notifications.html:7 +#, python-format +msgid "Invitation from %(user)s" +msgstr "" + +#: app/templates/partials/user_profile/middle_content.html:6 +msgid "Upcoming events" +msgstr "" + +#: app/templates/partials/user_profile/middle_content/event_card.html:3 +#, python-format +msgid "Upcoming event on %(date)s" +msgstr "" + +#: app/templates/partials/user_profile/middle_content/event_card.html:13 +#, python-format +msgid "%(title)s details" +msgstr "" + +#: app/templates/partials/user_profile/middle_content/update_event_modal.html:11 +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/calendar_privacy_modal.html:16 +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/description_modal.html:14 +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/email_modal.html:14 +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/name_modal.html:14 +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/telegram_modal.html:15 +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/upload_photo_modal.html:15 msgid "Save changes" msgstr "" -#: app/templates/profile.html:70 -msgid "Update email" +#: app/templates/partials/user_profile/sidebar_left/daily_horoscope.html:5 +msgid "Daily horoscope" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/features_card.html:5 +msgid "Explore more features" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/features_card.html:13 +msgid "Try PyLendar bot" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/features_card.html:20 +msgid "Add holidays to calendar" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/features_card.html:25 +msgid "Sync with Google" msgstr "" -#: app/templates/profile.html:90 +#: app/templates/partials/user_profile/sidebar_left/features_card/export_calendar.html:4 +msgid "Export my calendar" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/features_card/export_calendar.html:13 +msgid "Export" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/profile_update_menu.html:11 +msgid "Update full name" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/profile_update_menu.html:17 +msgid "Update email address" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/profile_update_menu.html:23 +msgid "Update profile photo" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/profile_update_menu.html:29 +msgid "Update profile status" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/profile_update_menu.html:35 +msgid "Register telegram ID" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/profile_update_menu.html:41 +msgid "Update calendar privacy" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/user_details.html:7 +msgid "Settings" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/calendar_privacy_modal.html:6 +msgid "Set your calendar privacy" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/description_modal.html:6 msgid "Update description" msgstr "" -#: app/templates/profile.html:111 +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/email_modal.html:6 +msgid "Update email" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/name_modal.html:6 +msgid "Update name" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/upload_photo_modal.html:6 msgid "Update photo" msgstr "" -#: app/templates/profile.html:220 -msgid "Upcoming event on (date)" +#: app/templates/partials/user_profile/sidebar_right/meetups.html:4 +msgid "Explore MeetUps near you" msgstr "" -#: app/templates/profile.html:246 -msgid "The Event (event)" +#: app/templates/partials/user_profile/sidebar_right/new_card.html:4 +msgid "Your card" msgstr "" -#: app/templates/profile.html:249 -msgid "Last updated (time) ago" +#: app/templates/salary/month.j2:7 +msgid "Monthly salary" msgstr "" -#: app/templates/profile.html:285 -msgid "Explore MeetUps near you" +#: app/templates/salary/month.j2:12 +msgid "Pick month:" msgstr "" -#: app/templates/profile.html:293 -msgid "Your Card" +#: app/templates/salary/month.j2:17 app/templates/salary/view.j2:25 +msgid "Bonus:" msgstr "" -#. i18n: String used in testing. Do not change. -#: tests/test_language.py:32 -msgid "test python translation" +#: app/templates/salary/month.j2:22 app/templates/salary/view.j2:30 +msgid "Deduction:" +msgstr "" + +#: app/templates/salary/month.j2:28 +msgid "Include weekly overtime" msgstr "" -#~ msgid "Features" -#~ msgstr "" +#: app/templates/salary/month.j2:31 +msgid "Calculate salary" +msgstr "" + +#: app/templates/salary/month.j2:35 app/templates/salary/pick.j2:7 +#: app/templates/salary/pick.j2:24 app/templates/salary/view.j2:41 +msgid "Edit settings" +msgstr "" + +#: app/templates/salary/pick.j2:9 app/templates/salary/pick.j2:26 +msgid "View salary" +msgstr "" + +#: app/templates/salary/pick.j2:15 app/templates/salary/settings.j2:20 +msgid "Please choose a category" +msgstr "" + +#: app/templates/salary/pick.j2:32 +msgid "Create settings for a different category" +msgstr "" + +#: app/templates/salary/settings.j2:8 +#, python-format +msgid "%(category)s salary settings" +msgstr "" + +#: app/templates/salary/settings.j2:10 +msgid "New salary settings" +msgstr "" + +#: app/templates/salary/settings.j2:28 +msgid "Create a new category" +msgstr "" + +#: app/templates/salary/settings.j2:33 +msgid "Hourly wage" +msgstr "" -#~ msgid "Export my calendar" -#~ msgstr "" +#: app/templates/salary/settings.j2:38 +msgid "Day off" +msgstr "" -#~ msgid "Settings" -#~ msgstr "" +#: app/templates/salary/settings.j2:40 +msgid "Friday" +msgstr "" -#~ msgid "Your feature" -#~ msgstr "" +#: app/templates/salary/settings.j2:41 +msgid "Saturday" +msgstr "" -#~ msgid "Calendar" -#~ msgstr "" +#: app/templates/salary/settings.j2:42 +msgid "Sunday" +msgstr "" -#~ msgid "Home" -#~ msgstr "" +#: app/templates/salary/settings.j2:47 +msgid "Holiday category:" +msgstr "" -#~ msgid "Profile" -#~ msgstr "" +#: app/templates/salary/settings.j2:56 +msgid "Regular hour basis" +msgstr "" -#~ msgid "Sign in" -#~ msgstr "" +#: app/templates/salary/settings.j2:61 +msgid "Night hour basis" +msgstr "" -#~ msgid "Sign up" -#~ msgstr "" +#: app/templates/salary/settings.j2:66 +msgid "Night shift start" +msgstr "" -#~ msgid "Agenda" -#~ msgstr "" +#: app/templates/salary/settings.j2:71 +msgid "Night shift end" +msgstr "" + +#: app/templates/salary/settings.j2:76 +msgid "Night shift minimum length" +msgstr "" + +#: app/templates/salary/settings.j2:81 +msgid "First overtime amount" +msgstr "" + +#: app/templates/salary/settings.j2:86 +msgid "First overtime pay" +msgstr "" + +#: app/templates/salary/settings.j2:91 +msgid "second overtime pay" +msgstr "" + +#: app/templates/salary/settings.j2:96 +msgid "Week working hour" +msgstr "" + +#: app/templates/salary/settings.j2:101 +msgid "Daily transport" +msgstr "" + +#: app/templates/salary/settings.j2:107 +msgid "Create salary settings" +msgstr "" + +#: app/templates/salary/settings.j2:109 +msgid "Update salary settings" +msgstr "" + +#: app/templates/salary/settings.j2:116 app/templates/salary/settings.j2:118 +msgid "View your salary" +msgstr "" + +#: app/templates/salary/settings.j2:118 +msgid "Setting created?" +msgstr "" + +#: app/templates/salary/settings.j2:118 +msgid "or" +msgstr "" + +#: app/templates/salary/settings.j2:118 +msgid "Edit your salary" +msgstr "" + +#: app/templates/salary/view.j2:13 +msgid "Hourly wage:" +msgstr "" + +#: app/templates/salary/view.j2:14 +msgid "Number of shifts:" +msgstr "" + +#: app/templates/salary/view.j2:15 +msgid "Base salary:" +msgstr "" + +#: app/templates/salary/view.j2:19 +msgid ">Weekly overtime total:" +msgstr "" + +#: app/templates/salary/view.j2:22 +msgid "Transport:" +msgstr "" + +#: app/templates/salary/view.j2:35 +msgid "Total salary:" +msgstr "" + +#. i18n: String used in testing. Do not change. +#: tests/test_language.py:32 +msgid "test python translation" +msgstr "" diff --git a/app/locales/he/LC_MESSAGES/base.mo b/app/locales/he/LC_MESSAGES/base.mo index 2233662f..1a143041 100644 Binary files a/app/locales/he/LC_MESSAGES/base.mo and b/app/locales/he/LC_MESSAGES/base.mo differ diff --git a/app/locales/he/LC_MESSAGES/base.po b/app/locales/he/LC_MESSAGES/base.po index 959b1f6d..15c5b56f 100644 --- a/app/locales/he/LC_MESSAGES/base.po +++ b/app/locales/he/LC_MESSAGES/base.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2021-02-07 21:31+0000\n" +"POT-Creation-Date: 2021-02-25 15:34+0200\n" "PO-Revision-Date: 2021-01-26 21:31+0200\n" "Last-Translator: FULL NAME \n" "Language: he\n" @@ -18,115 +18,638 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 2.9.0\n" -#: app/routers/profile.py:20 +#: app/internal/astronomy.py:11 +msgid "No response from server." +msgstr "" + +#: app/routers/export.py:18 msgid "Not found" msgstr "" -#: app/templates/agenda.html:11 +#: app/routers/search.py:53 +msgid "Invalid request." +msgstr "" + +#: app/routers/search.py:58 +msgid "No matching results for '{keywords}'." +msgstr "" + +#: app/templates/agenda.html:14 +#: app/templates/partials/user_profile/sidebar_left/features_card/export_calendar.html:9 msgid "From" msgstr "" -#: app/templates/agenda.html:13 -msgid "to" +#: app/templates/agenda.html:18 +#: app/templates/partials/user_profile/sidebar_left/features_card/export_calendar.html:11 +msgid "To" msgstr "" -#: app/templates/agenda.html:15 +#: app/templates/agenda.html:22 msgid "Get Agenda" msgstr "" -#: app/templates/agenda.html:20 +#: app/templates/agenda.html:28 +msgid "Filter events by category" +msgstr "" + +#: app/templates/agenda.html:33 +msgid "Filter by categories" +msgstr "" + +#: app/templates/agenda.html:39 msgid "Today" msgstr "" -#: app/templates/agenda.html:23 -msgid "Next week" +#: app/templates/agenda.html:42 +msgid "Next 7 days" msgstr "" -#: app/templates/agenda.html:26 -msgid "Next month" +#: app/templates/agenda.html:45 +msgid "Next 30 days" msgstr "" -#: app/templates/agenda.html:33 +#: app/templates/agenda.html:49 +msgid "Week graph" +msgstr "" + +#: app/templates/agenda.html:61 msgid "Start date is greater than end date" msgstr "" -#: app/templates/agenda.html:35 +#: app/templates/agenda.html:63 msgid "No events found..." msgstr "" -#: app/templates/profile.html:50 -msgid "Update name" +#: app/templates/agenda.html:77 +#: app/templates/partials/calendar/event/edit_event_details_tab.html:111 +#: app/templates/partials/calendar/event/view_event_details_tab.html:22 +msgid "Busy" +msgstr "" + +#: app/templates/agenda.html:77 +#: app/templates/partials/calendar/event/edit_event_details_tab.html:110 +#: app/templates/partials/calendar/event/view_event_details_tab.html:22 +msgid "Free" +msgstr "" + +#: app/templates/agenda.html:80 +#, python-format +msgid "duration: %(duration)s" +msgstr "" + +#: app/templates/archive.html:2 app/templates/archive.html:5 +msgid "Archived notifications" +msgstr "" + +#: app/templates/archive.html:13 +msgid "You don't have any archived notifications." +msgstr "" + +#: app/templates/base.html:41 +msgid "Sign Out" +msgstr "" + +#: app/templates/base.html:68 +msgid "About Us" +msgstr "" + +#: app/templates/notifications.html:2 +msgid "Notifications" +msgstr "" + +#: app/templates/notifications.html:5 +msgid "New notifications" +msgstr "" + +#: app/templates/notifications.html:13 +msgid "Mark all as read" +msgstr "" + +#: app/templates/notifications.html:23 +msgid "You don't have any new notifications." +msgstr "" + +#: app/templates/on_this_day.html:2 +msgid "On this day" +msgstr "" + +#: app/templates/on_this_day.html:11 +msgid "Explore more..." +msgstr "" + +#: app/templates/partials/base.html:38 +msgid "Pylendar" +msgstr "" + +#: app/templates/partials/calendar/event/comments_tab.html:2 +msgid "Write your comment here..." +msgstr "" + +#: app/templates/partials/calendar/event/comments_tab.html:3 +msgid "Post comment" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:8 +msgid "Voice" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:12 +msgid "Volume" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:17 +msgid "Pitch" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:22 +msgid "Rate" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:29 +msgid "Start date:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:30 +msgid "Start date" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:31 +msgid "Start time:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:32 +msgid "Start time" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:36 +msgid "End date:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:37 +msgid "End date" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:38 +msgid "End time:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:39 +msgid "End time" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:43 +#: app/templates/partials/calendar/event/edit_event_details_tab.html:44 +msgid "Location" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:54 +msgid "Title" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:59 +msgid "Item name" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:61 +msgid "Amount" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:63 +msgid "Participant" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:70 +msgid "Add an item" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:77 +msgid "Invited emails:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:78 +msgid "Invited emails, separated by commas" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:82 +msgid "Image:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:88 +msgid "Description:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:93 +msgid "Color:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:95 +msgid "Red" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:96 +msgid "Green" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:97 +msgid "Blue" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:102 +msgid "All-day:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:104 +#: app/templates/partials/calendar/event/edit_event_details_tab.html:135 +msgid "Yes" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:105 +#: app/templates/partials/calendar/event/edit_event_details_tab.html:136 +msgid "No" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:108 +msgid "Availability:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:114 +msgid "Privacy:" +msgstr "" + +#: app/templates/partials/calendar/event/edit_event_details_tab.html:133 +msgid "Google event:" +msgstr "" + +#: app/templates/partials/calendar/event/view_event_details_tab.html:61 +msgid "Choose user" +msgstr "" + +#: app/templates/partials/calendar/event/view_event_details_tab.html:75 +#: app/templates/partials/calendar/event/view_event_details_tab.html:82 +msgid "ICON" +msgstr "" + +#: app/templates/partials/calendar/event/view_event_details_tab.html:76 +#, python-format +msgid "Shared list: %(title)s" +msgstr "" + +#: app/templates/partials/calendar/event/view_event_details_tab.html:84 +#, python-format +msgid "Item %(index)s: %(name)s" +msgstr "" + +#: app/templates/partials/calendar/event/view_event_details_tab.html:85 +#, python-format +msgid "Amount: %(amount)s" +msgstr "" + +#: app/templates/partials/calendar/event/view_event_details_tab.html:86 +#, python-format +msgid "Participant: %(participant)s" +msgstr "" + +#: app/templates/partials/calendar/event/view_event_details_tab.html:94 +msgid "Duplicate" +msgstr "" + +#: app/templates/partials/calendar/event/view_event_details_tab.html:95 +msgid "Edit" +msgstr "" + +#: app/templates/partials/calendar/feature_settings/example.html:1 +msgid "Feature name" +msgstr "" + +#: app/templates/partials/index/navigation.html:10 +msgid "Home" +msgstr "" + +#: app/templates/partials/index/navigation.html:13 +msgid "Profile" +msgstr "" + +#: app/templates/partials/index/navigation.html:16 +msgid "Sign in" +msgstr "" + +#: app/templates/partials/index/navigation.html:19 +msgid "Sign up" msgstr "" -#: app/templates/profile.html:57 app/templates/profile.html:77 -#: app/templates/profile.html:97 app/templates/profile.html:120 -#: app/templates/profile.html:140 +#: app/templates/partials/index/navigation.html:24 +msgid "Agenda" +msgstr "" + +#: app/templates/partials/index/navigation.html:27 +msgid "Search" +msgstr "" + +#: app/templates/partials/index/navigation.html:30 +msgid "Audio settings" +msgstr "" + +#: app/templates/partials/index/navigation.html:33 +msgid "Categories" +msgstr "" + +#: app/templates/partials/index/navigation.html:36 +msgid "About us" +msgstr "" + +#: app/templates/partials/index/navigation.html:39 +msgid "Credits" +msgstr "" + +#: app/templates/partials/index/navigation.html:47 +msgid "Salary" +msgstr "" + +#: app/templates/partials/index/navigation.html:50 +msgid "Medication" +msgstr "" + +#: app/templates/partials/notification/generate_archive.html:7 +#: app/templates/partials/notification/generate_notifications.html:7 +#, python-format +msgid "Invitation from %(user)s" +msgstr "" + +#: app/templates/partials/user_profile/middle_content.html:6 +msgid "Upcoming events" +msgstr "" + +#: app/templates/partials/user_profile/middle_content/event_card.html:3 +#, python-format +msgid "Upcoming event on %(date)s" +msgstr "" + +#: app/templates/partials/user_profile/middle_content/event_card.html:13 +#, python-format +msgid "%(title)s details" +msgstr "" + +#: app/templates/partials/user_profile/middle_content/update_event_modal.html:11 +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/calendar_privacy_modal.html:16 +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/description_modal.html:14 +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/email_modal.html:14 +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/name_modal.html:14 +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/telegram_modal.html:15 +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/upload_photo_modal.html:15 msgid "Save changes" msgstr "" -#: app/templates/profile.html:70 -msgid "Update email" +#: app/templates/partials/user_profile/sidebar_left/daily_horoscope.html:5 +msgid "Daily horoscope" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/features_card.html:5 +msgid "Explore more features" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/features_card.html:13 +msgid "Try PyLendar bot" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/features_card.html:20 +msgid "Add holidays to calendar" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/features_card.html:25 +msgid "Sync with Google" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/features_card/export_calendar.html:4 +msgid "Export my calendar" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/features_card/export_calendar.html:13 +msgid "Export" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/profile_update_menu.html:11 +msgid "Update full name" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/profile_update_menu.html:17 +msgid "Update email address" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/profile_update_menu.html:23 +msgid "Update profile photo" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/profile_update_menu.html:29 +msgid "Update profile status" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/profile_update_menu.html:35 +msgid "Register telegram ID" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/profile_update_menu.html:41 +msgid "Update calendar privacy" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/user_details.html:7 +msgid "Settings" msgstr "" -#: app/templates/profile.html:90 +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/calendar_privacy_modal.html:6 +msgid "Set your calendar privacy" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/description_modal.html:6 msgid "Update description" msgstr "" -#: app/templates/profile.html:111 +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/email_modal.html:6 +msgid "Update email" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/name_modal.html:6 +msgid "Update name" +msgstr "" + +#: app/templates/partials/user_profile/sidebar_left/profile_card/modals/upload_photo_modal.html:6 msgid "Update photo" msgstr "" -#: app/templates/profile.html:220 -msgid "Upcoming event on (date)" +#: app/templates/partials/user_profile/sidebar_right/meetups.html:4 +msgid "Explore MeetUps near you" msgstr "" -#: app/templates/profile.html:246 -msgid "The Event (event)" +#: app/templates/partials/user_profile/sidebar_right/new_card.html:4 +msgid "Your card" msgstr "" -#: app/templates/profile.html:249 -msgid "Last updated (time) ago" +#: app/templates/salary/month.j2:7 +msgid "Monthly salary" msgstr "" -#: app/templates/profile.html:285 -msgid "Explore MeetUps near you" +#: app/templates/salary/month.j2:12 +msgid "Pick month:" msgstr "" -#: app/templates/profile.html:293 -msgid "Your Card" +#: app/templates/salary/month.j2:17 app/templates/salary/view.j2:25 +msgid "Bonus:" msgstr "" -#. i18n: String used in testing. Do not change. -#: tests/test_language.py:32 -msgid "test python translation" -msgstr "בדיקת תרגום בפייתון" +#: app/templates/salary/month.j2:22 app/templates/salary/view.j2:30 +msgid "Deduction:" +msgstr "" -#~ msgid "Features" -#~ msgstr "" +#: app/templates/salary/month.j2:28 +msgid "Include weekly overtime" +msgstr "" -#~ msgid "Export my calendar" -#~ msgstr "" +#: app/templates/salary/month.j2:31 +msgid "Calculate salary" +msgstr "" -#~ msgid "Settings" -#~ msgstr "" +#: app/templates/salary/month.j2:35 app/templates/salary/pick.j2:7 +#: app/templates/salary/pick.j2:24 app/templates/salary/view.j2:41 +msgid "Edit settings" +msgstr "" + +#: app/templates/salary/pick.j2:9 app/templates/salary/pick.j2:26 +msgid "View salary" +msgstr "" + +#: app/templates/salary/pick.j2:15 app/templates/salary/settings.j2:20 +msgid "Please choose a category" +msgstr "" + +#: app/templates/salary/pick.j2:32 +msgid "Create settings for a different category" +msgstr "" + +#: app/templates/salary/settings.j2:8 +#, python-format +msgid "%(category)s salary settings" +msgstr "" + +#: app/templates/salary/settings.j2:10 +msgid "New salary settings" +msgstr "" + +#: app/templates/salary/settings.j2:28 +msgid "Create a new category" +msgstr "" + +#: app/templates/salary/settings.j2:33 +msgid "Hourly wage" +msgstr "" + +#: app/templates/salary/settings.j2:38 +msgid "Day off" +msgstr "" -#~ msgid "Your feature" -#~ msgstr "" +#: app/templates/salary/settings.j2:40 +msgid "Friday" +msgstr "" -#~ msgid "Calendar" -#~ msgstr "" +#: app/templates/salary/settings.j2:41 +msgid "Saturday" +msgstr "" -#~ msgid "Home" -#~ msgstr "" +#: app/templates/salary/settings.j2:42 +msgid "Sunday" +msgstr "" -#~ msgid "Profile" -#~ msgstr "פרופיל" +#: app/templates/salary/settings.j2:47 +msgid "Holiday category:" +msgstr "" -#~ msgid "Sign in" -#~ msgstr "" +#: app/templates/salary/settings.j2:56 +msgid "Regular hour basis" +msgstr "" -#~ msgid "Sign up" -#~ msgstr "" +#: app/templates/salary/settings.j2:61 +msgid "Night hour basis" +msgstr "" + +#: app/templates/salary/settings.j2:66 +msgid "Night shift start" +msgstr "" -#~ msgid "Agenda" -#~ msgstr "" +#: app/templates/salary/settings.j2:71 +msgid "Night shift end" +msgstr "" + +#: app/templates/salary/settings.j2:76 +msgid "Night shift minimum length" +msgstr "" + +#: app/templates/salary/settings.j2:81 +msgid "First overtime amount" +msgstr "" + +#: app/templates/salary/settings.j2:86 +msgid "First overtime pay" +msgstr "" + +#: app/templates/salary/settings.j2:91 +msgid "second overtime pay" +msgstr "" + +#: app/templates/salary/settings.j2:96 +msgid "Week working hour" +msgstr "" + +#: app/templates/salary/settings.j2:101 +msgid "Daily transport" +msgstr "" + +#: app/templates/salary/settings.j2:107 +msgid "Create salary settings" +msgstr "" + +#: app/templates/salary/settings.j2:109 +msgid "Update salary settings" +msgstr "" + +#: app/templates/salary/settings.j2:116 app/templates/salary/settings.j2:118 +msgid "View your salary" +msgstr "" + +#: app/templates/salary/settings.j2:118 +msgid "Setting created?" +msgstr "" + +#: app/templates/salary/settings.j2:118 +msgid "or" +msgstr "" + +#: app/templates/salary/settings.j2:118 +msgid "Edit your salary" +msgstr "" + +#: app/templates/salary/view.j2:13 +msgid "Hourly wage:" +msgstr "" + +#: app/templates/salary/view.j2:14 +msgid "Number of shifts:" +msgstr "" + +#: app/templates/salary/view.j2:15 +msgid "Base salary:" +msgstr "" + +#: app/templates/salary/view.j2:19 +msgid ">Weekly overtime total:" +msgstr "" + +#: app/templates/salary/view.j2:22 +msgid "Transport:" +msgstr "" + +#: app/templates/salary/view.j2:35 +msgid "Total salary:" +msgstr "" + +#. i18n: String used in testing. Do not change. +#: tests/test_language.py:32 +msgid "test python translation" +msgstr "בדיקת תרגום בפייתון" diff --git a/app/main.py b/app/main.py index 67136680..4fb4793d 100644 --- a/app/main.py +++ b/app/main.py @@ -6,7 +6,6 @@ from fastapi.staticfiles import StaticFiles from sqlalchemy.orm import Session -import app.internal.features as internal_features from app import config from app.database import engine, models from app.dependencies import ( @@ -14,16 +13,13 @@ SOUNDS_PATH, STATIC_PATH, UPLOAD_PATH, - SessionLocal, 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.dependencies import get_jinja_current_user from app.internal.security.ouath2 import auth_exception_handler -from app.routers.notes import notes from app.routers.salary import routes as salary from app.utils.extending_openapi import custom_openapi @@ -53,7 +49,6 @@ def create_tables(engine, psql_environment): app.logger = logger app.add_exception_handler(status.HTTP_401_UNAUTHORIZED, auth_exception_handler) -templates.env.globals["jinja_current_user"] = get_jinja_current_user # This MUST come before the app.routers imports. set_ui_language() @@ -71,7 +66,6 @@ def create_tables(engine, psql_environment): email, event, export, - features, four_o_four, friendview, google_connect, @@ -82,11 +76,9 @@ def create_tables(engine, psql_environment): notification, profile, register, - reset_password, search, settings, telegram, - todo_list, user, weekview, weight, @@ -125,7 +117,6 @@ async def swagger_ui_redirect(): email.router, event.router, export.router, - features.router, four_o_four.router, friendview.router, google_connect.router, @@ -133,16 +124,13 @@ async def swagger_ui_redirect(): login.router, logout.router, meds.router, - notes.router, notification.router, profile.router, register.router, - reset_password.router, salary.router, search.router, settings.router, telegram.router, - todo_list.router, user.router, weekview.router, weight.router, @@ -153,13 +141,6 @@ async def swagger_ui_redirect(): app.include_router(router) -@app.on_event("startup") -async def startup_event(): - session = SessionLocal() - internal_features.create_features_at_startup(session=session) - session.close() - - # TODO: I add the quote day to the home page # until the relevant calendar view will be developed. @app.get("/", include_in_schema=False) diff --git a/app/resources/countries.py b/app/resources/countries.py deleted file mode 100644 index a50f0ff7..00000000 --- a/app/resources/countries.py +++ /dev/null @@ -1,639 +0,0 @@ -# List was taken from: -# https://gist.github.com/stasius12/f95f2999fa351212991c43a5f067c78d - -countries = [ - {'timezones': ['Asia/Kabul'], - 'code': 'AF', 'name': 'Afghanistan'}, - {'timezones': ['Europe/Mariehamn'], - 'code': 'AX', 'name': 'Aland Islands'}, - {'timezones': ['Europe/Tirane'], - 'code': 'AL', 'name': 'Albania'}, - {'timezones': ['Africa/Algiers'], - 'code': 'DZ', 'name': 'Algeria'}, - {'timezones': ['Pacific/Pago_Pago'], - 'code': 'AS', 'name': 'American Samoa'}, - {'timezones': ['Europe/Andorra'], - 'code': 'AD', 'name': 'Andorra'}, - {'timezones': ['Africa/Luanda'], - 'code': 'AO', 'name': 'Angola'}, - {'timezones': ['America/Anguilla'], - 'code': 'AI', 'name': 'Anguilla'}, - {'timezones': [ - 'Antarctica/Casey', - 'Antarctica/Davis', 'Antarctica/DumontDUrville', - 'Antarctica/Mawson', 'Antarctica/McMurdo', - 'Antarctica/Palmer', 'Antarctica/Rothera', - 'Antarctica/Syowa', 'Antarctica/Troll', - 'Antarctica/Vostok'], - 'code': 'AQ', - 'name': 'Antarctica'}, - {'timezones': ['America/Antigua'], - 'code': 'AG', 'name': 'Antigua and Barbuda'}, - {'timezones': [ - 'America/Argentina/Buenos_Aires', - 'America/Argentina/Catamarca', - 'America/Argentina/Cordoba', - 'America/Argentina/Jujuy', - 'America/Argentina/La_Rioja', - 'America/Argentina/Mendoza', - 'America/Argentina/Rio_Gallegos', - 'America/Argentina/Salta', - 'America/Argentina/San_Juan', - 'America/Argentina/San_Luis', - 'America/Argentina/Tucuman', - 'America/Argentina/Ushuaia'], - 'code': 'AR', 'name': 'Argentina'}, - {'timezones': ['Asia/Yerevan'], - 'code': 'AM', 'name': 'Armenia'}, - {'timezones': ['America/Aruba'], - 'code': 'AW', 'name': 'Aruba'}, - {'timezones': [ - 'Antarctica/Macquarie', - 'Australia/Adelaide', 'Australia/Brisbane', - 'Australia/Broken_Hill', 'Australia/Currie', - 'Australia/Darwin', 'Australia/Eucla', - 'Australia/Hobart', 'Australia/Lindeman', - 'Australia/Lord_Howe', 'Australia/Melbourne', - 'Australia/Perth', 'Australia/Sydney'], - 'code': 'AU', 'name': 'Australia'}, - {'timezones': ['Europe/Vienna'], - 'code': 'AT', 'name': 'Austria'}, - {'timezones': ['Asia/Baku'], - 'code': 'AZ', 'name': 'Azerbaijan'}, - {'timezones': ['America/Nassau'], - 'code': 'BS', 'name': 'Bahamas'}, - {'timezones': ['Asia/Bahrain'], - 'code': 'BH', 'name': 'Bahrain'}, - {'timezones': ['Asia/Dhaka'], - 'code': 'BD', 'name': 'Bangladesh'}, - {'timezones': ['America/Barbados'], - 'code': 'BB', 'name': 'Barbados'}, - {'timezones': ['Europe/Minsk'], - 'code': 'BY', 'name': 'Belarus'}, - {'timezones': ['Europe/Brussels'], - 'code': 'BE', 'name': 'Belgium'}, - {'timezones': ['America/Belize'], - 'code': 'BZ', 'name': 'Belize'}, - {'timezones': ['Africa/Porto-Novo'], - 'code': 'BJ', 'name': 'Benin'}, - {'timezones': ['Atlantic/Bermuda'], - 'code': 'BM', 'name': 'Bermuda'}, - {'timezones': ['Asia/Thimphu'], - 'code': 'BT', 'name': 'Bhutan'}, - {'timezones': ['America/La_Paz'], - 'code': 'BO', 'name': 'Bolivia'}, - {'timezones': ['America/Kralendijk'], - 'code': 'BQ', 'name': 'Bonaire, Saint Eustatius and Saba '}, - {'timezones': ['Europe/Sarajevo'], - 'code': 'BA', 'name': 'Bosnia and Herzegovina'}, - {'timezones': ['Africa/Gaborone'], - 'code': 'BW', 'name': 'Botswana'}, - {'timezones': [ - 'America/Araguaina', - 'America/Bahia', 'America/Belem', - 'America/Boa_Vista', 'America/Campo_Grande', - 'America/Cuiaba', 'America/Eirunepe', - 'America/Fortaleza', 'America/Maceio', - 'America/Manaus', 'America/Noronha', - 'America/Porto_Velho', 'America/Recife', - 'America/Rio_Branco', 'America/Santarem', - 'America/Sao_Paulo'], - 'code': 'BR', 'name': 'Brazil'}, - {'timezones': ['Indian/Chagos'], - 'code': 'IO', 'name': 'British Indian Ocean Territory'}, - {'timezones': ['America/Tortola'], - 'code': 'VG', 'name': 'British Virgin Islands'}, - {'timezones': ['Asia/Brunei'], - 'code': 'BN', 'name': 'Brunei'}, - {'timezones': ['Europe/Sofia'], - 'code': 'BG', 'name': 'Bulgaria'}, - {'timezones': ['Africa/Ouagadougou'], - 'code': 'BF', 'name': 'Burkina Faso'}, - {'timezones': ['Africa/Bujumbura'], - 'code': 'BI', 'name': 'Burundi'}, - {'timezones': ['Asia/Phnom_Penh'], - 'code': 'KH', 'name': 'Cambodia'}, - {'timezones': ['Africa/Douala'], - 'code': 'CM', 'name': 'Cameroon'}, - {'timezones': [ - 'America/Atikokan', - 'America/Blanc-Sablon', 'America/Cambridge_Bay', - 'America/Montreal', 'America/Creston', - 'America/Dawson', 'America/Dawson_Creek', - 'America/Edmonton', 'America/Fort_Nelson', - 'America/Glace_Bay', 'America/Goose_Bay', - 'America/Halifax', 'America/Inuvik', - 'America/Iqaluit', 'America/Moncton', - 'America/Nipigon', 'America/Pangnirtung', - 'America/Rainy_River', 'America/Rankin_Inlet', - 'America/Regina', 'America/Resolute', - 'America/St_Johns', 'America/Swift_Current', - 'America/Thunder_Bay', 'America/Toronto', - 'America/Vancouver', 'America/Whitehorse', - 'America/Winnipeg', 'America/Yellowknife'], - 'code': 'CA', 'name': 'Canada'}, - {'timezones': ['Atlantic/Cape_Verde'], - 'code': 'CV', 'name': 'Cape Verde'}, - {'timezones': ['America/Cayman'], - 'code': 'KY', 'name': 'Cayman Islands'}, - {'timezones': ['Africa/Bangui'], - 'code': 'CF', 'name': 'Central African Republic'}, - {'timezones': ['Africa/Ndjamena'], - 'code': 'TD', 'name': 'Chad'}, - {'timezones': [ - 'America/Punta_Arenas', - 'America/Santiago', 'Pacific/Easter'], - 'code': 'CL', 'name': 'Chile'}, - {'timezones': [ - 'Asia/Shanghai', - 'Asia/Urumqi', 'Asia/Chungking', - 'Asia/Chongqing'], - 'code': 'CN', 'name': 'China'}, - {'timezones': ['Indian/Christmas'], - 'code': 'CX', 'name': 'Christmas Island'}, - {'timezones': ['Indian/Cocos'], - 'code': 'CC', 'name': 'Cocos Islands'}, - {'timezones': ['America/Bogota'], - 'code': 'CO', 'name': 'Colombia'}, - {'timezones': ['Indian/Comoro'], - 'code': 'KM', 'name': 'Comoros'}, - {'timezones': ['Pacific/Rarotonga'], - 'code': 'CK', 'name': 'Cook Islands'}, - {'timezones': ['America/Costa_Rica'], - 'code': 'CR', 'name': 'Costa Rica'}, - {'timezones': ['Europe/Zagreb'], - 'code': 'HR', 'name': 'Croatia'}, - {'timezones': ['America/Havana'], - 'code': 'CU', 'name': 'Cuba'}, - {'timezones': ['America/Curacao'], - 'code': 'CW', 'name': 'Curacao'}, - {'timezones': [ - 'Asia/Famagusta', - 'Asia/Nicosia'], - 'code': 'CY', 'name': 'Cyprus'}, - {'timezones': ['Europe/Prague'], - 'code': 'CZ', 'name': 'Czech Republic'}, - {'timezones': [ - 'Africa/Kinshasa', - 'Africa/Lubumbashi'], - 'code': 'CD', - 'name': 'Democratic Republic of the Congo'}, - {'timezones': ['Europe/Copenhagen'], - 'code': 'DK', 'name': 'Denmark'}, - {'timezones': ['Africa/Djibouti'], - 'code': 'DJ', 'name': 'Djibouti'}, - {'timezones': ['America/Dominica'], - 'code': 'DM', 'name': 'Dominica'}, - {'timezones': ['America/Santo_Domingo'], - 'code': 'DO', 'name': 'Dominican Republic'}, - {'timezones': ['Asia/Dili'], - 'code': 'TL', 'name': 'East Timor'}, - {'timezones': [ - 'America/Guayaquil', - 'Pacific/Galapagos'], - 'code': 'EC', - 'name': 'Ecuador'}, - {'timezones': ['Africa/Cairo'], - 'code': 'EG', 'name': 'Egypt'}, - {'timezones': ['America/El_Salvador'], - 'code': 'SV', 'name': 'El Salvador'}, - {'timezones': ['Africa/Malabo'], - 'code': 'GQ', 'name': 'Equatorial Guinea'}, - {'timezones': ['Africa/Asmara'], - 'code': 'ER', 'name': 'Eritrea'}, - {'timezones': ['Europe/Tallinn'], - 'code': 'EE', 'name': 'Estonia'}, - {'timezones': ['Africa/Addis_Ababa'], - 'code': 'ET', 'name': 'Ethiopia'}, - {'timezones': ['Atlantic/Stanley'], - 'code': 'FK', 'name': 'Falkland Islands'}, - {'timezones': ['Atlantic/Faroe'], - 'code': 'FO', 'name': 'Faroe Islands'}, - {'timezones': ['Pacific/Fiji'], - 'code': 'FJ', 'name': 'Fiji'}, - {'timezones': ['Europe/Helsinki'], - 'code': 'FI', 'name': 'Finland'}, - {'timezones': ['Europe/Paris'], - 'code': 'FR', 'name': 'France'}, - {'timezones': ['America/Cayenne'], - 'code': 'GF', 'name': 'French Guiana'}, - {'timezones': [ - 'Pacific/Gambier', - 'Pacific/Marquesas', 'Pacific/Tahiti'], - 'code': 'PF', 'name': 'French Polynesia'}, - {'timezones': ['Indian/Kerguelen'], - 'code': 'TF', 'name': 'French Southern Territories'}, - {'timezones': ['Africa/Libreville'], - 'code': 'GA', 'name': 'Gabon'}, - {'timezones': ['Africa/Banjul'], - 'code': 'GM', 'name': 'Gambia'}, - {'timezones': ['Asia/Tbilisi'], - 'code': 'GE', 'name': 'Georgia'}, - {'timezones': [ - 'Europe/Berlin', - 'Europe/Busingen'], - 'code': 'DE', 'name': 'Germany'}, - {'timezones': ['Africa/Accra'], - 'code': 'GH', 'name': 'Ghana'}, - {'timezones': ['Europe/Gibraltar'], - 'code': 'GI', 'name': 'Gibraltar'}, - {'timezones': ['Europe/Athens'], - 'code': 'GR', 'name': 'Greece'}, - {'timezones': [ - 'America/Danmarkshavn', - 'America/Godthab', 'America/Scoresbysund', - 'America/Thule'], - 'code': 'GL', 'name': 'Greenland'}, - {'timezones': ['America/Grenada'], - 'code': 'GD', 'name': 'Grenada'}, - {'timezones': ['America/Guadeloupe'], - 'code': 'GP', 'name': 'Guadeloupe'}, - {'timezones': ['Pacific/Guam'], - 'code': 'GU', 'name': 'Guam'}, - {'timezones': ['America/Guatemala'], - 'code': 'GT', 'name': 'Guatemala'}, - {'timezones': ['Europe/Guernsey'], - 'code': 'GG', 'name': 'Guernsey'}, - {'timezones': ['Africa/Conakry'], - 'code': 'GN', 'name': 'Guinea'}, - {'timezones': ['Africa/Bissau'], - 'code': 'GW', 'name': 'Guinea-Bissau'}, - {'timezones': ['America/Guyana'], - 'code': 'GY', 'name': 'Guyana'}, - {'timezones': ['America/Port-au-Prince'], - 'code': 'HT', 'name': 'Haiti'}, - {'timezones': ['America/Tegucigalpa'], - 'code': 'HN', 'name': 'Honduras'}, - {'timezones': ['Asia/Hong_Kong'], - 'code': 'HK', 'name': 'Hong Kong'}, - {'timezones': ['Europe/Budapest'], - 'code': 'HU', 'name': 'Hungary'}, - {'timezones': ['Atlantic/Reykjavik'], - 'code': 'IS', 'name': 'Iceland'}, - {'timezones': [ - 'Asia/Kolkata', - 'Asia/Calcutta'], - 'code': 'IN', 'name': 'India'}, - {'timezones': [ - 'Asia/Jakarta', - 'Asia/Jayapura', 'Asia/Makassar', - 'Asia/Pontianak'], - 'code': 'ID', 'name': 'Indonesia'}, - {'timezones': ['Asia/Tehran'], - 'code': 'IR', 'name': 'Iran'}, - {'timezones': ['Asia/Baghdad'], - 'code': 'IQ', 'name': 'Iraq'}, - {'timezones': ['Europe/Dublin'], - 'code': 'IE', 'name': 'Ireland'}, - {'timezones': ['Europe/Isle_of_Man'], - 'code': 'IM', 'name': 'Isle of Man'}, - {'timezones': ['Asia/Jerusalem'], - 'code': 'IL', 'name': 'Israel'}, - {'timezones': ['Europe/Rome'], - 'code': 'IT', 'name': 'Italy'}, - {'timezones': ['Africa/Abidjan'], - 'code': 'CI', 'name': 'Ivory Coast'}, - {'timezones': ['America/Jamaica'], - 'code': 'JM', 'name': 'Jamaica'}, - {'timezones': ['Asia/Tokyo'], - 'code': 'JP', 'name': 'Japan'}, - {'timezones': ['Europe/Jersey'], - 'code': 'JE', 'name': 'Jersey'}, - {'timezones': ['Asia/Amman'], - 'code': 'JO', 'name': 'Jordan'}, - {'timezones': [ - 'Asia/Almaty', - 'Asia/Aqtau', 'Asia/Aqtobe', - 'Asia/Atyrau', 'Asia/Oral', - 'Asia/Qyzylorda'], - 'code': 'KZ', - 'name': 'Kazakhstan'}, - {'timezones': ['Africa/Nairobi'], - 'code': 'KE', 'name': 'Kenya'}, - {'timezones': [ - 'Pacific/Enderbury', - 'Pacific/Kiritimati', 'Pacific/Tarawa'], - 'code': 'KI', 'name': 'Kiribati'}, - {'timezones': ['Asia/Kuwait'], - 'code': 'KW', 'name': 'Kuwait'}, - {'timezones': ['Asia/Bishkek'], - 'code': 'KG', 'name': 'Kyrgyzstan'}, - {'timezones': ['Asia/Vientiane'], - 'code': 'LA', 'name': 'Laos'}, - {'timezones': ['Europe/Riga'], - 'code': 'LV', 'name': 'Latvia'}, - {'timezones': ['Asia/Beirut'], - 'code': 'LB', 'name': 'Lebanon'}, - {'timezones': ['Africa/Maseru'], - 'code': 'LS', 'name': 'Lesotho'}, - {'timezones': ['Africa/Monrovia'], - 'code': 'LR', 'name': 'Liberia'}, - {'timezones': ['Africa/Tripoli'], - 'code': 'LY', 'name': 'Libya'}, - {'timezones': ['Europe/Vaduz'], - 'code': 'LI', 'name': 'Liechtenstein'}, - {'timezones': ['Europe/Vilnius'], - 'code': 'LT', 'name': 'Lithuania'}, - {'timezones': ['Europe/Luxembourg'], - 'code': 'LU', 'name': 'Luxembourg'}, - {'timezones': ['Asia/Macau'], - 'code': 'MO', 'name': 'Macao'}, - {'timezones': ['Europe/Skopje'], - 'code': 'MK', 'name': 'Macedonia'}, - {'timezones': ['Indian/Antananarivo'], - 'code': 'MG', 'name': 'Madagascar'}, - {'timezones': ['Africa/Blantyre'], - 'code': 'MW', 'name': 'Malawi'}, - {'timezones': [ - 'Asia/Kuala_Lumpur', - 'Asia/Kuching'], - 'code': 'MY', 'name': 'Malaysia'}, - {'timezones': ['Indian/Maldives'], - 'code': 'MV', 'name': 'Maldives'}, - {'timezones': ['Africa/Bamako'], - 'code': 'ML', 'name': 'Mali'}, - {'timezones': ['Europe/Malta'], - 'code': 'MT', 'name': 'Malta'}, - {'timezones': [ - 'Pacific/Kwajalein', - 'Pacific/Majuro'], - 'code': 'MH', - 'name': 'Marshall Islands'}, - {'timezones': ['America/Martinique'], - 'code': 'MQ', 'name': 'Martinique'}, - {'timezones': ['Africa/Nouakchott'], - 'code': 'MR', 'name': 'Mauritania'}, - {'timezones': ['Indian/Mauritius'], - 'code': 'MU', 'name': 'Mauritius'}, - {'timezones': ['Indian/Mayotte'], - 'code': 'YT', 'name': 'Mayotte'}, - {'timezones': [ - 'America/Bahia_Banderas', - 'America/Cancun', 'America/Chihuahua', - 'America/Hermosillo', 'America/Matamoros', - 'America/Mazatlan', 'America/Merida', - 'America/Mexico_City', 'America/Monterrey', - 'America/Ojinaga', 'America/Tijuana'], - 'code': 'MX', 'name': 'Mexico'}, - {'timezones': [ - 'Pacific/Chuuk', - 'Pacific/Kosrae', 'Pacific/Pohnpei'], - 'code': 'FM', 'name': 'Micronesia'}, - {'timezones': ['Europe/Chisinau'], - 'code': 'MD', 'name': 'Moldova'}, - {'timezones': ['Europe/Monaco'], - 'code': 'MC', 'name': 'Monaco'}, - {'timezones': [ - 'Asia/Choibalsan', - 'Asia/Hovd', 'Asia/Ulaanbaatar'], - 'code': 'MN', 'name': 'Mongolia'}, - {'timezones': ['Europe/Podgorica'], - 'code': 'ME', 'name': 'Montenegro'}, - {'timezones': ['America/Montserrat'], - 'code': 'MS', 'name': 'Montserrat'}, - {'timezones': ['Africa/Casablanca'], - 'code': 'MA', 'name': 'Morocco'}, - {'timezones': ['Africa/Maputo'], - 'code': 'MZ', 'name': 'Mozambique'}, - {'timezones': ['Africa/Windhoek'], - 'code': 'NA', 'name': 'Namibia'}, - {'timezones': ['Pacific/Nauru'], - 'code': 'NR', 'name': 'Nauru'}, - {'timezones': [ - 'Asia/Kathmandu', - 'Asia/Katmandu'], - 'code': 'NP', - 'name': 'Nepal'}, - {'timezones': ['Europe/Amsterdam'], - 'code': 'NL', 'name': 'Netherlands'}, - {'timezones': ['Pacific/Noumea'], - 'code': 'NC', 'name': 'New Caledonia'}, - {'timezones': [ - 'Pacific/Auckland', - 'Pacific/Chatham'], - 'code': 'NZ', - 'name': 'New Zealand'}, - {'timezones': ['America/Managua'], - 'code': 'NI', 'name': 'Nicaragua'}, - {'timezones': ['Africa/Niamey'], - 'code': 'NE', 'name': 'Niger'}, - {'timezones': ['Africa/Lagos'], - 'code': 'NG', 'name': 'Nigeria'}, - {'timezones': ['Pacific/Niue'], - 'code': 'NU', 'name': 'Niue'}, - {'timezones': ['Pacific/Norfolk'], - 'code': 'NF', 'name': 'Norfolk Island'}, - {'timezones': ['Asia/Pyongyang'], - 'code': 'KP', 'name': 'North Korea'}, - {'timezones': ['Pacific/Saipan'], - 'code': 'MP', 'name': 'Northern Mariana Islands'}, - {'timezones': ['Europe/Oslo'], - 'code': 'NO', 'name': 'Norway'}, - {'timezones': ['Asia/Muscat'], - 'code': 'OM', 'name': 'Oman'}, - {'timezones': ['Asia/Karachi'], - 'code': 'PK', 'name': 'Pakistan'}, - {'timezones': ['Pacific/Palau'], - 'code': 'PW', 'name': 'Palau'}, - {'timezones': ['Asia/Gaza', 'Asia/Hebron'], - 'code': 'PS', 'name': 'Palestinian Territory'}, - {'timezones': ['America/Panama'], - 'code': 'PA', 'name': 'Panama'}, - {'timezones': [ - 'Pacific/Bougainville', - 'Pacific/Port_Moresby'], - 'code': 'PG', 'name': 'Papua New Guinea'}, - {'timezones': ['America/Asuncion'], - 'code': 'PY', 'name': 'Paraguay'}, - {'timezones': ['America/Lima'], - 'code': 'PE', 'name': 'Peru'}, - {'timezones': ['Asia/Manila'], - 'code': 'PH', 'name': 'Philippines'}, - {'timezones': ['Pacific/Pitcairn'], - 'code': 'PN', 'name': 'Pitcairn'}, - {'timezones': ['Europe/Warsaw'], - 'code': 'PL', 'name': 'Poland'}, - {'timezones': [ - 'Atlantic/Azores', - 'Atlantic/Madeira', 'Europe/Lisbon'], - 'code': 'PT', 'name': 'Portugal'}, - {'timezones': ['America/Puerto_Rico'], - 'code': 'PR', 'name': 'Puerto Rico'}, - {'timezones': ['Asia/Qatar'], - 'code': 'QA', 'name': 'Qatar'}, - {'timezones': ['Africa/Brazzaville'], - 'code': 'CG', 'name': 'Republic of the Congo'}, - {'timezones': ['Indian/Reunion'], - 'code': 'RE', 'name': 'Reunion'}, - {'timezones': ['Europe/Bucharest'], - 'code': 'RO', 'name': 'Romania'}, - {'timezones': [ - 'Asia/Anadyr', 'Asia/Barnaul', - 'Asia/Chita', 'Asia/Irkutsk', 'Asia/Kamchatka', - 'Asia/Khandyga', 'Asia/Krasnoyarsk', 'Asia/Magadan', - 'Asia/Novokuznetsk', 'Asia/Novosibirsk', 'Asia/Omsk', - 'Asia/Sakhalin', 'Asia/Srednekolymsk', 'Asia/Tomsk', - 'Asia/Ust-Nera', 'Asia/Vladivostok', 'Asia/Yakutsk', - 'Asia/Yekaterinburg', 'Europe/Astrakhan', - 'Europe/Kaliningrad', 'Europe/Kirov', - 'Europe/Moscow', 'Europe/Samara', - 'Europe/Saratov', 'Europe/Simferopol', - 'Europe/Ulyanovsk', 'Europe/Volgograd'], - 'code': 'RU', 'name': 'Russia'}, - {'timezones': ['Africa/Kigali'], - 'code': 'RW', 'name': 'Rwanda'}, - {'timezones': ['America/St_Barthelemy'], - 'code': 'BL', 'name': 'Saint Barthelemy'}, - {'timezones': ['Atlantic/St_Helena'], - 'code': 'SH', 'name': 'Saint Helena'}, - {'timezones': ['America/St_Kitts'], - 'code': 'KN', 'name': 'Saint Kitts and Nevis'}, - {'timezones': ['America/St_Lucia'], - 'code': 'LC', 'name': 'Saint Lucia'}, - {'timezones': ['America/Marigot'], - 'code': 'MF', 'name': 'Saint Martin'}, - {'timezones': ['America/Miquelon'], - 'code': 'PM', 'name': 'Saint Pierre and Miquelon'}, - {'timezones': ['America/St_Vincent'], - 'code': 'VC', 'name': 'Saint Vincent and the Grenadines'}, - {'timezones': ['Pacific/Apia'], - 'code': 'WS', 'name': 'Samoa'}, - {'timezones': ['Europe/San_Marino'], - 'code': 'SM', 'name': 'San Marino'}, - {'timezones': ['Africa/Sao_Tome'], - 'code': 'ST', 'name': 'Sao Tome and Principe'}, - {'timezones': ['Asia/Riyadh'], - 'code': 'SA', 'name': 'Saudi Arabia'}, - {'timezones': ['Africa/Dakar'], - 'code': 'SN', 'name': 'Senegal'}, - {'timezones': ['Europe/Belgrade'], - 'code': 'RS', 'name': 'Serbia'}, - {'timezones': ['Indian/Mahe'], - 'code': 'SC', 'name': 'Seychelles'}, - {'timezones': ['Africa/Freetown'], - 'code': 'SL', 'name': 'Sierra Leone'}, - {'timezones': ['Asia/Singapore'], - 'code': 'SG', 'name': 'Singapore'}, - {'timezones': ['America/Lower_Princes'], - 'code': 'SX', 'name': 'Sint Maarten'}, - {'timezones': ['Europe/Bratislava'], - 'code': 'SK', 'name': 'Slovakia'}, - {'timezones': ['Europe/Ljubljana'], - 'code': 'SI', 'name': 'Slovenia'}, - {'timezones': ['Pacific/Guadalcanal'], - 'code': 'SB', 'name': 'Solomon Islands'}, - {'timezones': ['Africa/Mogadishu'], - 'code': 'SO', 'name': 'Somalia'}, - {'timezones': ['Africa/Johannesburg'], - 'code': 'ZA', 'name': 'South Africa'}, - {'timezones': ['Atlantic/South_Georgia'], - 'code': 'GS', - 'name': 'South Georgia and the South Sandwich Islands'}, - {'timezones': ['Asia/Seoul'], - 'code': 'KR', 'name': 'South Korea'}, - {'timezones': ['Africa/Juba'], - 'code': 'SS', 'name': 'South Sudan'}, - {'timezones': [ - 'Africa/Ceuta', - 'Atlantic/Canary', 'Europe/Madrid'], - 'code': 'ES', 'name': 'Spain'}, - {'timezones': ['Asia/Colombo'], - 'code': 'LK', 'name': 'Sri Lanka'}, - {'timezones': ['Africa/Khartoum'], - 'code': 'SD', 'name': 'Sudan'}, - {'timezones': ['America/Paramaribo'], - 'code': 'SR', 'name': 'Suriname'}, - {'timezones': ['Arctic/Longyearbyen'], - 'code': 'SJ', 'name': 'Svalbard and Jan Mayen'}, - {'timezones': ['Africa/Mbabane'], - 'code': 'SZ', 'name': 'Swaziland'}, - {'timezones': ['Europe/Stockholm'], - 'code': 'SE', 'name': 'Sweden'}, - {'timezones': ['Europe/Zurich'], - 'code': 'CH', 'name': 'Switzerland'}, - {'timezones': ['Asia/Damascus'], - 'code': 'SY', 'name': 'Syria'}, - {'timezones': ['Asia/Taipei'], - 'code': 'TW', 'name': 'Taiwan'}, - {'timezones': ['Asia/Dushanbe'], - 'code': 'TJ', 'name': 'Tajikistan'}, - {'timezones': ['Africa/Dar_es_Salaam'], - 'code': 'TZ', 'name': 'Tanzania'}, - {'timezones': ['Asia/Bangkok'], - 'code': 'TH', 'name': 'Thailand'}, - {'timezones': ['Africa/Lome'], - 'code': 'TG', 'name': 'Togo'}, - {'timezones': ['Pacific/Fakaofo'], - 'code': 'TK', 'name': 'Tokelau'}, - {'timezones': ['Pacific/Tongatapu'], - 'code': 'TO', 'name': 'Tonga'}, - {'timezones': ['America/Port_of_Spain'], - 'code': 'TT', 'name': 'Trinidad and Tobago'}, - {'timezones': ['Africa/Tunis'], - 'code': 'TN', 'name': 'Tunisia'}, - {'timezones': ['Europe/Istanbul'], - 'code': 'TR', 'name': 'Turkey'}, - {'timezones': ['Asia/Ashgabat'], - 'code': 'TM', 'name': 'Turkmenistan'}, - {'timezones': ['America/Grand_Turk'], - 'code': 'TC', 'name': 'Turks and Caicos Islands'}, - {'timezones': ['Pacific/Funafuti'], - 'code': 'TV', 'name': 'Tuvalu'}, - {'timezones': ['America/St_Thomas'], - 'code': 'VI', 'name': 'U.S. Virgin Islands'}, - {'timezones': ['Africa/Kampala'], - 'code': 'UG', 'name': 'Uganda'}, - {'timezones': [ - 'Europe/Kiev', - 'Europe/Uzhgorod', 'Europe/Zaporozhye'], - 'code': 'UA', 'name': 'Ukraine'}, - {'timezones': ['Asia/Dubai'], - 'code': 'AE', 'name': 'United Arab Emirates'}, - {'timezones': ['Europe/London'], - 'code': 'GB', 'name': 'United Kingdom'}, - {'timezones': [ - 'America/Adak', - 'America/Anchorage', 'America/Boise', - 'America/Chicago', 'US/Central', - 'America/Denver', 'America/Detroit', - 'US/Michigan', 'America/Indiana/Indianapolis', - 'America/Indiana/Knox', 'America/Indiana/Marengo', - 'America/Indiana/Petersburg', 'America/Indiana/Tell_City', - 'America/Indiana/Vevay', 'America/Indiana/Vincennes', - 'America/Indiana/Winamac', 'America/Juneau', - 'America/Kentucky/Louisville', 'America/Kentucky/Monticello', - 'America/Los_Angeles', 'US/Pacific', 'America/Menominee', - 'America/Metlakatla', 'America/New_York', 'US/Eastern', - 'America/Nome', 'America/North_Dakota/Beulah', - 'America/North_Dakota/Center', - 'America/North_Dakota/New_Salem', - 'America/Phoenix', 'America/Sitka', - 'America/Yakutat', 'Pacific/Honolulu'], - 'code': 'US', 'name': 'United States'}, - {'timezones': ['Pacific/Midway', 'Pacific/Wake'], - 'code': 'UM', 'name': 'United States Minor Outlying Islands'}, - {'timezones': ['America/Montevideo'], - 'code': 'UY', 'name': 'Uruguay'}, - {'timezones': ['Asia/Samarkand', 'Asia/Tashkent'], - 'code': 'UZ', 'name': 'Uzbekistan'}, - {'timezones': ['Pacific/Efate'], - 'code': 'VU', 'name': 'Vanuatu'}, - {'timezones': ['Europe/Vatican'], - 'code': 'VA', 'name': 'Vatican'}, - {'timezones': ['America/Caracas'], - 'code': 'VE', 'name': 'Venezuela'}, - {'timezones': ['Asia/Ho_Chi_Minh', 'Asia/Saigon', 'Asia/Hanoi'], - 'code': 'VN', 'name': 'Vietnam'}, - {'timezones': ['Pacific/Wallis'], - 'code': 'WF', 'name': 'Wallis and Futuna'}, - {'timezones': ['Africa/El_Aaiun'], - 'code': 'EH', 'name': 'Western Sahara'}, - {'timezones': ['Asia/Aden'], - 'code': 'YE', 'name': 'Yemen'}, - {'timezones': ['Africa/Lusaka'], - 'code': 'ZM', 'name': 'Zambia'}, - {'timezones': ['Africa/Harare'], - 'code': 'ZW', 'name': 'Zimbabwe'}, - {'timezones': ['Asia/Rangoon', 'Asia/Yangon'], - 'code': 'MM', 'name': 'Myanmar'}, -] diff --git a/app/routers/calendar_grid.py b/app/routers/calendar_grid.py index 9ef5202c..98e94016 100644 --- a/app/routers/calendar_grid.py +++ b/app/routers/calendar_grid.py @@ -51,19 +51,6 @@ def display(self) -> str: """Returns day date inf the format of 00 MONTH 00""" return self.date.strftime("%d %B %y").upper() - def dayview_format(self) -> str: - """Returns day date in the format of yyyy-mm-dd""" - return self.date.strftime("%Y-%m-%d") - - def weekview_format(self) -> str: - """Returns the first day of week in the format of yyyy-mm-dd""" - day = self.date - if day.strftime("%A") == "Sunday": - return self.dayview_format() - while day.strftime("%A") != "Sunday": - day -= timedelta(days=1) - return day.strftime("%Y-%m-%d") - def set_id(self) -> str: """Returns day date inf the format of 00-month-0000""" return self.date.strftime("%d-%B-%Y") diff --git a/app/routers/event.py b/app/routers/event.py index edc28a2f..d81f0756 100644 --- a/app/routers/event.py +++ b/app/routers/event.py @@ -19,9 +19,7 @@ from app.config import PICTURE_EXTENSION from app.database.models import ( - Category, Comment, - Country, Event, SharedList, SharedListItem, @@ -32,7 +30,6 @@ from app.internal import comment as cmt from app.internal.emotion import get_emotion from app.internal.event import ( - get_all_countries_names, get_invited_emails, get_location_coordinates, get_messages, @@ -40,7 +37,6 @@ raise_if_zoom_link_invalid, ) from app.internal.privacy import PrivacyKinds -from app.internal.security.dependencies import current_user from app.internal.utils import create_model, get_current_user from app.routers.categories import get_user_categories @@ -106,28 +102,20 @@ async def create_event_api(event: EventModel, session=Depends(get_db)): return {"success": True} -def get_categories_list( - user: User, - db_session: Session = Depends(get_db), -) -> List[Category]: - return get_user_categories(db_session, user.user_id) - - @router.get("/edit", include_in_schema=False) +@router.get("/edit") async def eventedit( request: Request, db_session: Session = Depends(get_db), - user: User = Depends(current_user), ) -> Response: - countries_names = get_all_countries_names(db_session) - categories_list = get_categories_list(user, db_session) + user_id = 1 # until issue#29 will get current user_id from session + categories_list = get_user_categories(db_session, user_id) return templates.TemplateResponse( "eventedit.html", { "request": request, "categories_list": categories_list, "privacy": PrivacyKinds, - "countries_names": countries_names, }, ) @@ -150,6 +138,7 @@ async def create_new_event( availability = data.get("availability", "True") == "True" location = data["location"] all_day = data["event_type"] and data["event_type"] == "on" + vc_link = data.get("vc_link") category_id = data.get("category_id") privacy = data["privacy"] @@ -788,23 +777,3 @@ async def delete_comment( cmt.delete_comment(db, comment_id) path = router.url_path_for("view_comments", event_id=str(event_id)) return RedirectResponse(path, status_code=303) - - -@router.get("/timezone/country/{country_name}", include_in_schema=False) -async def check_timezone( - country_name, - request: Request, - db_session: Session = Depends(get_db), -) -> Response: - try: - country_timezone = ( - db_session.query(Country.timezone) - .filter_by(name=country_name) - .first()[0] - ) - except TypeError: - raise HTTPException( - status_code=404, - detail="The inserted country name is not found", - ) - return {"timezone": country_timezone} diff --git a/app/routers/export.py b/app/routers/export.py index baa098a6..0fa5b279 100644 --- a/app/routers/export.py +++ b/app/routers/export.py @@ -27,11 +27,13 @@ def export( user: CurrentUser = Depends(current_user), ) -> StreamingResponse: """Returns the Export page route. + Args: start_date: A date or an empty string. end_date: A date or an empty string. db: Optional; The database connection. user: user schema object. + Returns: A StreamingResponse that contains an .ics file. """ diff --git a/app/routers/features.py b/app/routers/features.py deleted file mode 100644 index 1694afe3..00000000 --- a/app/routers/features.py +++ /dev/null @@ -1,111 +0,0 @@ -from typing import List - -from fastapi import APIRouter, Depends, Request -from sqlalchemy.sql import exists - -from app.database.models import Feature, User, UserFeature -from app.dependencies import SessionLocal, get_db -from app.internal.features import ( - create_user_feature_association, - get_user_installed_features, - get_user_uninstalled_features, - is_user_has_feature, -) -from app.internal.security.dependencies import current_user - -router = APIRouter( - prefix="/features", - tags=["features"], - responses={404: {"description": "Not found"}}, -) - - -@router.get("/") -async def index( - request: Request, - session: SessionLocal = Depends(get_db), -) -> List[Feature]: - features = session.query(Feature).all() - return features - - -@router.post("/add") -async def add_feature_to_user( - request: Request, - session: SessionLocal = Depends(get_db), - user: User = Depends(current_user), -) -> bool: - form = await request.form() - - feat = session.query( - exists().where(Feature.id == form["feature_id"]), - ).scalar() - - is_exist = is_user_has_feature( - session=session, - feature_id=form["feature_id"], - user_id=user.user_id, - ) - - if not feat or is_exist: - # in case there is no feature in the database with that same id - # and or the association is exist - return False - - create_user_feature_association( - db=session, - feature_id=form["feature_id"], - user_id=user.user_id, - is_enable=True, - ) - - return is_user_has_feature( - session=session, - feature_id=form["feature_id"], - user_id=user.user_id, - ) - - -@router.post("/delete") -async def delete_user_feature_association( - request: Request, - session: SessionLocal = Depends(get_db), - user: User = Depends(current_user), -) -> bool: - form = await request.form() - feature_id = int(form["feature_id"]) - - is_exist = is_user_has_feature( - session=session, - feature_id=feature_id, - user_id=user.user_id, - ) - - if not is_exist: - return False - - session.query(UserFeature).filter_by( - feature_id=feature_id, - user_id=user.user_id, - ).delete() - session.commit() - - return True - - -@router.get("/deactive") -def deactive( - request: Request, - session: SessionLocal = Depends(get_db), - user: User = Depends(current_user), -): - return get_user_uninstalled_features(user_id=user.user_id, session=session) - - -@router.get("/active") -def active( - request: Request, - session: SessionLocal = Depends(get_db), - user: User = Depends(current_user), -): - return get_user_installed_features(user_id=user.user_id, session=session) diff --git a/app/routers/google_connect.py b/app/routers/google_connect.py index 6892cff9..ccbd8d3f 100644 --- a/app/routers/google_connect.py +++ b/app/routers/google_connect.py @@ -3,7 +3,6 @@ from starlette.responses import RedirectResponse from app.dependencies import get_db -from app.internal.features import feature_access_filter from app.internal.google_connect import fetch_save_events, get_credentials from app.internal.utils import get_current_user from app.routers.profile import router as profile @@ -16,7 +15,6 @@ @router.get("/sync") -@feature_access_filter async def google_sync( request: Request, session=Depends(get_db), diff --git a/app/routers/notes/__init__.py b/app/routers/notes/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/app/routers/notes/notes.py b/app/routers/notes/notes.py deleted file mode 100644 index fd2c38c3..00000000 --- a/app/routers/notes/notes.py +++ /dev/null @@ -1,167 +0,0 @@ -from typing import Any, Dict, List, Sequence - -from fastapi import APIRouter, HTTPException, Request, status -from fastapi.param_functions import Depends -from fastapi.responses import Response -from sqlalchemy.orm import Session -from starlette.responses import RedirectResponse - -from app.database.schemas import NoteDB, NoteSchema -from app.dependencies import get_db, templates -from app.internal import utils -from app.internal.notes import notes - -router = APIRouter( - prefix="/notes", - tags=["notes"], - responses={404: {"description": "Not found"}}, -) - - -@router.post( - "/edit/{note_id}", - status_code=status.HTTP_202_ACCEPTED, - include_in_schema=False, -) -async def redirect_update_note( - request: Request, - note_id: int, - session: Session = Depends(get_db), -) -> RedirectResponse: - """Update a note using user-interface form.""" - form = await request.form() - updated_note = NoteSchema(**dict(form)) - await update_note(updated_note, note_id, session) - return RedirectResponse("/notes", status_code=status.HTTP_302_FOUND) - - -@router.post( - "/delete/{note_id}", - status_code=status.HTTP_200_OK, - include_in_schema=False, -) -async def redirect_delete_note( - note_id: int, - session: Session = Depends(get_db), -) -> RedirectResponse: - """Delete a note from the database using user-interface form.""" - await delete_note(note_id, session) - return RedirectResponse("/notes", status_code=status.HTTP_302_FOUND) - - -@router.post( - "/add", - status_code=status.HTTP_201_CREATED, - include_in_schema=False, -) -async def create_note_by_form( - request: Request, - session: Session = Depends(get_db), -) -> RedirectResponse: - """Add a note using user-interface form.""" - form = await request.form() - new_note = NoteSchema(**dict(form)) - new_note.creator = utils.get_current_user(session) - await notes.create_note(note=new_note, session=session) - return RedirectResponse("/notes", status_code=status.HTTP_302_FOUND) - - -@router.post("/", response_model=NoteDB, status_code=status.HTTP_201_CREATED) -async def create_new_note( - request: NoteSchema, - session: Session = Depends(get_db), -) -> Dict[str, Any]: - """Create a note in the database.""" - new_note = NoteSchema(**dict(request)) - new_note.creator = utils.get_current_user(session) - return await notes.create_note(note=new_note, session=session) - - -@router.delete("/{note_id}/", status_code=status.HTTP_200_OK) -async def delete_note(note_id: int, session: Session = Depends(get_db)) -> str: - """Delete a note by its identifier.""" - return await notes.delete(session, note_id) - - -@router.put("/{note_id}/", status_code=status.HTTP_202_ACCEPTED) -async def update_note( - request: NoteSchema, - note_id: int, - session: Session = Depends(get_db), -) -> str: - """Update a note by providing its identifier and the changed json data.""" - return await notes.update(request, session, note_id) - - -@router.get("/view/{note_id}", include_in_schema=False) -async def view_note( - request: Request, - note_id: int, - session: Session = Depends(get_db), -) -> Response: - """View a note for update using user interface.""" - note = await notes.view(session, note_id) - return templates.TemplateResponse( - "notes/note_view.html", - {"request": request, "data": note}, - ) - - -@router.get("/delete/{note_id}", include_in_schema=False) -async def remove_note( - request: Request, - note_id: int, - session: Session = Depends(get_db), -) -> Response: - """View a note for delete using user interface.""" - note = await notes.view(session, note_id) - return templates.TemplateResponse( - "notes/note_delete.html", - {"request": request, "data": note}, - ) - - -@router.get("/add", include_in_schema=False) -async def create_note_form(request: Request) -> Response: - """View form for creating a new note.""" - return templates.TemplateResponse("notes/note.html", {"request": request}) - - -@router.get("/all", response_model=List[NoteDB]) -async def get_all_notes( - session: Session = Depends(get_db), -) -> Sequence[NoteDB]: - """View all notes in the database.""" - return await notes.get_all(session) - - -@router.get( - "/{note_id}/", - status_code=status.HTTP_200_OK, - response_model=NoteDB, -) -async def read_note( - note_id: int, - session: Session = Depends(get_db), -) -> NoteDB: - """View a note by its identifier.""" - note = await notes.view(session, note_id) - if not note: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Note with id {note_id} not found", - ) - return note - - -@router.get("/", include_in_schema=False) -async def view_notes( - request: Request, - session: Session = Depends(get_db), -) -> Response: - """View all notes in the database using user interface.""" - data = await notes.get_all(session) - return templates.TemplateResponse( - "notes/notes.html", - {"request": request, "data": data}, - ) diff --git a/app/routers/register.py b/app/routers/register.py index f66661e1..11927359 100644 --- a/app/routers/register.py +++ b/app/routers/register.py @@ -7,9 +7,10 @@ from starlette.status import HTTP_302_FOUND from starlette.templating import _TemplateResponse -from app.database import schemas +from app.database import models, schemas from app.dependencies import get_db, templates -from app.internal.user.user import check_unique_fields, create_user +from app.internal.security.ouath2 import get_hashed_password +from app.internal.utils import save router = APIRouter( prefix="", @@ -18,6 +19,63 @@ ) +def _create_user(session, **kw) -> models.User: + """Creates and saves a new user.""" + user = models.User(**kw) + save(session, user) + return user + + +async def create_user(db: Session, user: schemas.UserCreate) -> models.User: + """ + creating a new User object in the database, with hashed password + """ + unhashed_password = user.password.encode("utf-8") + hashed_password = get_hashed_password(unhashed_password) + user_details = { + "username": user.username, + "full_name": user.full_name, + "email": user.email, + "password": hashed_password, + "description": user.description, + "language_id": user.language_id, + "target_weight": user.target_weight, + } + return _create_user(**user_details, session=db) + + +async def check_unique_fields( + db: Session, + new_user: schemas.UserCreate, +) -> dict: + """Verifying new user details are unique. Return relevant errors""" + errors = {} + if db.query( + db.query(models.User) + .filter(models.User.username == new_user.username) + .exists(), + ).scalar(): + errors["username"] = "That username is already taken" + if db.query( + db.query(models.User) + .filter(models.User.email == new_user.email) + .exists(), + ).scalar(): + errors["email"] = "Email already registered" + return errors + + +def get_error_messages_by_fields( + errors: List[Dict[str, Any]], +) -> Dict[str, str]: + """Getting validation errors by fields from pydantic ValidationError""" + errors_by_fields = {error["loc"][0]: error["msg"] for error in errors} + return { + field_name: f"{field_name.capitalize()} {error_message}" + for field_name, error_message in errors_by_fields.items() + } + + @router.get("/register") async def register_user_form(request: Request) -> _TemplateResponse: """rendering register route get method""" @@ -54,14 +112,3 @@ async def register( ) await create_user(db=db, user=new_user) return RedirectResponse("/profile", status_code=HTTP_302_FOUND) - - -def get_error_messages_by_fields( - errors: List[Dict[str, Any]], -) -> Dict[str, str]: - """Getting validation errors by fields from pydantic ValidationError""" - errors_by_fields = {error["loc"][0]: error["msg"] for error in errors} - return { - field_name: f"{field_name.capitalize()} {error_message}" - for field_name, error_message in errors_by_fields.items() - } diff --git a/app/routers/reset_password.py b/app/routers/reset_password.py deleted file mode 100644 index 9a8d3444..00000000 --- a/app/routers/reset_password.py +++ /dev/null @@ -1,137 +0,0 @@ -from typing import Optional - -from fastapi import APIRouter, Depends, Request -from pydantic import ValidationError -from sqlalchemy.orm import Session -from starlette.responses import RedirectResponse -from starlette.status import HTTP_302_FOUND - -from app.dependencies import get_db, templates -from app.internal.email import BackgroundTasks, send_reset_password_mail -from app.internal.security.ouath2 import ( - create_jwt_token, - get_jwt_token, - is_email_compatible_to_username, - update_password, -) -from app.internal.security.schema import ForgotPassword, ResetPassword -from app.routers.login import router as login_router - -router = APIRouter( - prefix="", - tags=["/reset_password"], - responses={404: {"description": "Not found"}}, -) - - -@router.get("/forgot-password") -async def forgot_password_form(request: Request) -> templates: - """rendering forgot password form get method""" - return templates.TemplateResponse( - "forgot_password.html", - { - "request": request, - }, - ) - - -@router.post("/forgot-password") -async def forgot_password( - request: Request, - background_tasks: BackgroundTasks, - db: Session = Depends(get_db), -) -> templates: - """ - Validaiting form data fields. - Validaiting form data against database records. - If all validations succeed, creating jwt token, - then sending email to the user with a reset password route link. - The contains the verafiction jwt token. - """ - form = await request.form() - form_dict = dict(form) - form_dict["username"] = "@" + form_dict["username"] - try: - # validating form data by creating pydantic schema object - user = ForgotPassword(**form_dict) - except ValidationError: - return templates.TemplateResponse( - "forgot_password.html", - {"request": request, "message": "Please check your credentials"}, - ) - user = await is_email_compatible_to_username(db, user) - if not user: - return templates.TemplateResponse( - "forgot_password.html", - {"request": request, "message": "Please check your credentials"}, - ) - user.email_verification_token = create_jwt_token(user, jwt_min_exp=15) - await send_reset_password_mail(user, background_tasks) - return templates.TemplateResponse( - "forgot_password.html", - { - "request": request, - "message": "Email for reseting password was sent", - }, - ) - - -@router.get("/reset-password") -async def reset_password_form( - request: Request, - email_verification_token: Optional[str] = "", -) -> templates: - """ - Rendering reset password form get method. - Validating jwt token is supplied with request. - """ - if email_verification_token: - return templates.TemplateResponse( - "reset_password.html", - { - "request": request, - }, - ) - message = "?message=Verification token is missing" - return RedirectResponse( - login_router.url_path_for("login_user_form") + f"{message}", - status_code=HTTP_302_FOUND, - ) - - -@router.post("/reset-password") -async def reset_password( - request: Request, - email_verification_token: str = "", - db: Session = Depends(get_db), -) -> RedirectResponse: - """ - Receives email verification jwt token. - Receives form data, and validates all fields are correct. - Validating token. - validatting form data against token details. - If all validations succeed, hashing new password and updating database. - """ - jwt_payload = get_jwt_token(email_verification_token) - jwt_username = jwt_payload.get("sub").strip("@") - form = await request.form() - form_dict = dict(form) - validated = True - if not form_dict["username"] == jwt_username: - validated = False - try: - # validating form data by creating pydantic schema object - user = ResetPassword(**form_dict) - except ValueError: - validated = False - if not validated: - return templates.TemplateResponse( - "reset_password.html", - {"request": request, "message": "Please check your credentials"}, - ) - await update_password(db, jwt_username, user.password) - message = "?message=Success reset password" - return RedirectResponse( - login_router.url_path_for("login_user_form") + str(message), - status_code=HTTP_302_FOUND, - ) diff --git a/app/routers/share.py b/app/routers/share.py index d916915c..fe5d449e 100644 --- a/app/routers/share.py +++ b/app/routers/share.py @@ -4,22 +4,22 @@ from app.database.models import Event, Invitation from app.internal.export import get_icalendar -from app.internal.user import user +from app.routers.user import does_user_exist, get_users def sort_emails( - participants: List[str], - session: Session, + participants: List[str], + session: Session, ) -> Dict[str, List[str]]: """Sorts emails to registered and unregistered users.""" - emails = {"registered": [], "unregistered": []} # type: ignore + emails = {'registered': [], 'unregistered': []} # type: ignore for participant in participants: - if user.does_user_exist(email=participant, session=session): - temp: list = emails["registered"] + if does_user_exist(email=participant, session=session): + temp: list = emails['registered'] else: - temp: list = emails["unregistered"] + temp: list = emails['unregistered'] temp.append(participant) @@ -27,8 +27,8 @@ def sort_emails( def send_email_invitation( - participants: List[str], - event: Event, + participants: List[str], + event: Event, ) -> bool: """Sends an email with an invitation.""" if participants: @@ -40,15 +40,15 @@ def send_email_invitation( def send_in_app_invitation( - participants: List[str], - event: Event, - session: Session, + participants: List[str], + event: Event, + session: Session ) -> bool: """Sends an in-app invitation for registered users.""" for participant in participants: # email is unique - recipient = user.get_users(email=participant, session=session)[0] + recipient = get_users(email=participant, session=session)[0] if recipient.id != event.owner.id: session.add(Invitation(recipient=recipient, event=event)) @@ -63,10 +63,9 @@ def send_in_app_invitation( def share(event: Event, participants: List[str], session: Session) -> bool: """Sends invitations to all event participants.""" - registered, unregistered = sort_emails( - participants, - session=session, - ).values() + registered, unregistered = ( + sort_emails(participants, session=session).values() + ) if send_email_invitation(unregistered, event): if send_in_app_invitation(registered, event, session): return True diff --git a/app/routers/todo_list.py b/app/routers/todo_list.py deleted file mode 100644 index eb9b3468..00000000 --- a/app/routers/todo_list.py +++ /dev/null @@ -1,143 +0,0 @@ -from datetime import datetime - -from fastapi import APIRouter, Depends, Form, status -from fastapi.encoders import jsonable_encoder -from fastapi.responses import JSONResponse, RedirectResponse -from sqlalchemy.exc import SQLAlchemyError -from sqlalchemy.orm import Session -from starlette.requests import Request - -from app.config import templates -from app.dependencies import get_db -from app.internal.todo_list import by_id, create_task -from app.internal.utils import get_current_user - -router = APIRouter( - prefix="/task", - tags=["task"], - responses={status.HTTP_404_NOT_FOUND: {"description": "Not found"}}, -) - - -@router.post("/delete") -def delete_task( - request: Request, - task_id: int = Form(...), - db: Session = Depends(get_db), -) -> RedirectResponse: - user = get_current_user(db) - task = by_id(db, task_id) - if task.owner_id != user.id: - return templates.TemplateResponse( - "calendar_day_view.html", - {"task_id": task_id}, - status_code=status.HTTP_403_FORBIDDEN, - ) - - date_str = task.date.strftime('%Y-%m-%d') - try: - # Delete task - db.delete(task) - - db.commit() - - except (SQLAlchemyError, TypeError): - return templates.TemplateResponse( - "calendar_day_view.html", - {"task_id": task_id}, - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - ) - return RedirectResponse( - request.url_for("dayview", date=date_str), - status_code=status.HTTP_302_FOUND, - ) - - -@router.post("/add") -async def add_task( - request: Request, - title: str = Form(...), - description: str = Form(...), - date_str: str = Form(...), - time_str: str = Form(...), - is_important: bool = Form(False), - session: Session = Depends(get_db), -) -> RedirectResponse: - user = get_current_user(session) - create_task( - session, - title, - description, - datetime.strptime(date_str, '%Y-%m-%d').date(), - datetime.strptime(time_str, '%H:%M').time(), - user.id, - is_important, - ) - return RedirectResponse( - request.url_for("dayview", date=date_str), - status_code=status.HTTP_303_SEE_OTHER, - ) - - -@router.post("/edit") -async def edit_task( - request: Request, - task_id: int = Form(...), - title: str = Form(...), - description: str = Form(...), - date_str: str = Form(...), - time_str: str = Form(...), - is_important: bool = Form(False), - session: Session = Depends(get_db), -) -> RedirectResponse: - task = by_id(session, task_id) - task.title = title - task.description = description - task.date = datetime.strptime(date_str, '%Y-%m-%d').date() - task.time = datetime.strptime(time_str, '%H:%M:%S').time() - task.is_important = is_important - session.commit() - return RedirectResponse( - request.url_for("dayview", date=date_str), - status_code=status.HTTP_303_SEE_OTHER, - ) - - -@router.post("/done/{task_id}") -async def set_task_done( - request: Request, - task_id: int, - session: Session = Depends(get_db), -) -> RedirectResponse: - task = by_id(session, task_id) - task.is_done = True - session.commit() - return RedirectResponse( - request.url_for("dayview", date=task.date.strftime('%Y-%m-%d')), - status_code=status.HTTP_303_SEE_OTHER, - ) - - -@router.post("/undone/{task_id}") -async def set_task_undone( - request: Request, - task_id: int, - session: Session = Depends(get_db), -) -> RedirectResponse: - task = by_id(session, task_id) - task.is_done = False - session.commit() - return RedirectResponse( - request.url_for("dayview", date=task.date.strftime('%Y-%m-%d')), - status_code=status.HTTP_303_SEE_OTHER, - ) - - -@router.get("/{task_id}") -async def get_task( - task_id: int, - session: Session = Depends(get_db), -) -> JSONResponse: - task = by_id(session, task_id) - data = jsonable_encoder(task) - return JSONResponse(content=data) diff --git a/app/routers/user.py b/app/routers/user.py index 1e69f7a6..8b8a0403 100644 --- a/app/routers/user.py +++ b/app/routers/user.py @@ -1,36 +1,76 @@ -from fastapi import APIRouter, Depends, HTTPException, Request, status +from typing import List + +from fastapi import APIRouter, Depends, Request +from pydantic import BaseModel, Field +from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session from starlette.responses import RedirectResponse from starlette.status import HTTP_200_OK -from app.database.models import User +from app.database.models import Event, User, UserEvent from app.dependencies import get_db from app.internal.user.availability import disable, enable from app.internal.utils import get_current_user router = APIRouter( - prefix="/users", - tags=["users"], + prefix="/user", + tags=["user"], responses={404: {"description": "Not found"}}, ) -@router.get("/{id}", status_code=status.HTTP_200_OK) -async def get_user(id: int, session=Depends(get_db)): - user = session.query(User).filter_by(id=id).first() - if not user: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"User with id {id} not found", - ) - return user +class UserModel(BaseModel): + username: str + password: str + email: str = Field(regex="^\\S+@\\S+\\.\\S+$") + language: str + language_id: int -@router.get("/") +@router.get("/list") async def get_all_users(session=Depends(get_db)): return session.query(User).all() +@router.get("/") +async def get_user(id: int, session=Depends(get_db)): + return session.query(User).filter_by(id=id).first() + + +def get_users(session: Session, **param): + """Returns all users filtered by param.""" + try: + users = list(session.query(User).filter_by(**param)) + except SQLAlchemyError: + return [] + else: + return users + + +def does_user_exist( + session: Session, *, user_id=None, username=None, email=None +): + """Returns True if user exists, False otherwise. + function can receive one of the there parameters""" + if user_id: + return len(get_users(session=session, id=user_id)) == 1 + if username: + return len(get_users(session=session, username=username)) == 1 + if email: + return len(get_users(session=session, email=email)) == 1 + return False + + +def get_all_user_events(session: Session, user_id: int) -> List[Event]: + """Returns all events that the user participants in.""" + return ( + session.query(Event) + .join(UserEvent) + .filter(UserEvent.user_id == user_id) + .all() + ) + + @router.post("/disable") def disable_logged_user(request: Request, session: Session = Depends(get_db)): """route that sends request to disable the user. diff --git a/app/routers/weekview.py b/app/routers/weekview.py index 1c683632..a95f746e 100644 --- a/app/routers/weekview.py +++ b/app/routers/weekview.py @@ -7,7 +7,7 @@ from sqlalchemy.orm.session import Session from app.database.models import Event, User -from app.dependencies import TEMPLATES_PATH, get_db +from app.dependencies import get_db, templates from app.internal.security.dependencies import current_user from app.routers.dayview import ( CurrentTimeAttributes, @@ -17,8 +17,6 @@ get_events_and_attributes, ) -templates = Jinja2Templates(directory=TEMPLATES_PATH) - router = APIRouter() diff --git a/app/static/credits_pictures/profile.PNG b/app/static/credits_pictures/profile.png similarity index 100% rename from app/static/credits_pictures/profile.PNG rename to app/static/credits_pictures/profile.png diff --git a/app/static/credits_style.css b/app/static/credits_style.css index ba371c4f..398db3c9 100644 --- a/app/static/credits_style.css +++ b/app/static/credits_style.css @@ -1,4 +1,6 @@ body { + margin-left: 6.25em; + margin-right: 6.25em; background-color: var(--backgroundcol); } diff --git a/app/static/dayview.css b/app/static/dayview.css index 3cde2aa8..4b13cbb6 100644 --- a/app/static/dayview.css +++ b/app/static/dayview.css @@ -4,11 +4,12 @@ body { flex-direction: column; overflow: hidden; } -.day-view-class { +#day-view { display: flex; flex: 1; flex-direction: column; position: relative; + overflow-y: hidden; } #top-tab { @@ -23,8 +24,6 @@ body { margin-bottom: var(--space_xs); line-height: 1; overflow-y: scroll; - -ms-overflow-style: none; - scrollbar-width: none; } .schedule::-webkit-scrollbar { @@ -47,20 +46,7 @@ body { grid-row: 1 / -1; grid-column: 1 / -1; display: grid; - grid-template-rows: repeat(100, auto); -} - -.timegrid { - grid-row: 1 / -1; - grid-column: 1 / -1; - display: grid; - grid-template-rows: repeat(100, auto); - z-index: 43; -} - -.sub-timegrid { - display: grid; - grid-template-rows: repeat(15, auto); + grid-template-rows: repeat(100, 1fr); } .hour-block { @@ -167,8 +153,6 @@ body { position: absolute; right: 0.5rem; bottom: 0.5rem; - margin-bottom: 1.5em; - z-index: 60; } .plus_image { @@ -180,110 +164,3 @@ body { width: 1.2rem; height: 1.2rem; } - -#current_time_cursor { - border-bottom: 2.5px dotted rgba(255, 0, 0, 0.808); -} - -#all-day-events { - background-color: var(--primary); - word-spacing: 0.25em; -} - - -.todo-style { - background: #F7F7F7; -} - -#table { - max-width: 31.25em; -} - -.open-button { - background-color: var(--primary); - max-width: 10%; - color: #fff; - margin-top: 0.75em; - margin-left: 45.5%; - font-size: 1.25em; - border-radius: 20px; - transition-duration: 0.4s; - box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2), 0 6px 20px 0 rgba(0,0,0,0.19); -} - -.open-button:hover { - background-color: #ccddff; - color: #00001a; - box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19); -} - -.top-todo { - background: #2B346A; -} - -.todo-style h1 { - color: #fff; - text-align: center; -} - -.important { - border-bottom: 5px solid blue; - margin-left: 3em; -} - -.todo-style input[type="text"], -.todo-style input[type="date"], -.todo-style input[type="time"], -.todo-style textarea { - -webkit-transition: all 0.30s ease-in-out; - -moz-transition: all 0.30s ease-in-out; - -ms-transition: all 0.30s ease-in-out; - -o-transition: all 0.30s ease-in-out; - outline: none; - box-sizing: border-box; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - width: 100%; - background: #fff; - margin-bottom: 4%; - border: 1px solid #ccc; - padding: 3%; - color: #555; -} - -.todo-style input[type="text"]:focus, -.todo-style input[type="date"]:focus, -.todo-style input[type="time"]:focus, -.todo-style textarea:focus { - box-shadow: 0 0 5px #43D1AF; - padding: 3%; - border: 1px solid #43D1AF; -} - -.todo-style th, .todo-style td { - padding-right: 3em; - padding-top: 2em; - text-align: left; -} - -.todo-style button { - border-radius: 20px; - padding: 2%; - text-align: right; - background-color: #80bfff; -} - - -.edit { - text-decoration: none; - background-color: #eafaf6; - border-style: solid; - border-color: #004d99; - border-radius: 10px; -} - -.todo-style button:hover { - background-color: var(--primary); - color: white; - box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19); -} \ No newline at end of file diff --git a/app/static/event/check_country_time.js b/app/static/event/check_country_time.js deleted file mode 100644 index 27605579..00000000 --- a/app/static/event/check_country_time.js +++ /dev/null @@ -1,84 +0,0 @@ -function checkCountryTime() { - const ERROR_TIME_DURATION = 3000; - const TIME_NOT_FILLED = 'Please enter the meeting date and time'; - const COUNTRY_NOT_FILLED = 'Please choose country'; - const modal_container = document.querySelector('.countries-modal-container'); - const open_modal = document.querySelector('.open-countries-modal'); - const close_modal = document.querySelector('.close-countries-modal'); - const submit_country = document.querySelector('.check-time'); - const upper_result = document.querySelector('.upper-result'); - const start_result = document.querySelector('.start-result'); - const end_result = document.querySelector('.end-result'); - const error_msg = document.querySelector('.empty-fields-error'); - const user_timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; - - function getStartDate() { - const start_date = document.getElementById('start_date').value; - return start_date; - } - - function getStartTime() { - const start_time = document.getElementById('start_time').value; - return start_time; - } - - function getEndDate() { - const end_date = document.getElementById('end_date').value; - return end_date; - } - - function getEndTime() { - const end_time = document.getElementById('end_time').value; - return end_time; - } - - function displayErrorMsg(content) { - error_msg.classList.remove('empty-fields-error-disappear'); - error_msg.innerText = content; - setTimeout(() => error_msg.classList.add('empty-fields-error-disappear'), ERROR_TIME_DURATION); - } - - function convertTimes(data, chosen_country) { - const start_datetime = new Date(getStartDate() + 'T' + getStartTime()); - let converted_start_time = start_datetime.toLocaleTimeString('en-US', {timeZone: data.timezone}); - upper_result.innerText = 'Meeting Time in ' + chosen_country + ' is:'; - start_result.innerText = converted_start_time; - if (!(getEndDate() === "" || getEndTime() === "")) { - const end_datetime = new Date(getEndDate() + 'T' + getEndTime()); - let converted_end_time = end_datetime.toLocaleTimeString('en-US', {timeZone: data.timezone}); - end_result.innerText = ' until ' + converted_end_time; - } - } - - open_modal.addEventListener('click', (event) => { - event.preventDefault(); - modal_container.classList.add('modal-active'); - if (getStartDate() === '' || getStartTime() === '') { - displayErrorMsg(TIME_NOT_FILLED); - } - }); - - submit_country.addEventListener('click', (event) => { - event.preventDefault(); - if (getStartDate() === '' || getStartTime() === '') { - displayErrorMsg(TIME_NOT_FILLED); - return; - } - const chosen_country = document.getElementById('countries-datalist').value; - if (chosen_country === '') { - displayErrorMsg(COUNTRY_NOT_FILLED); - return; - } - fetch(`/event/timezone/country/${chosen_country}`) - .then(response => response.json()) - .then(data => convertTimes(data, chosen_country)) - }); - - close_modal.addEventListener('click', (event) => { - event.preventDefault(); - modal_container.classList.remove('modal-active'); - }); -} - - -document.addEventListener("DOMContentLoaded", checkCountryTime); diff --git a/app/static/event/eventedit.css b/app/static/event/eventedit.css index daca1757..0193595b 100644 --- a/app/static/event/eventedit.css +++ b/app/static/event/eventedit.css @@ -66,103 +66,6 @@ input[type="submit"] { width: 100%; } -.countries-modal-container { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100vh; - background-color: rgba(0 , 0, 0, 0.5); - display: flex; - justify-content: center; - align-items: center; - visibility: hidden; - opacity: 0; - transition: visibility 0s, opacity 0, 5s; - font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; -} - -.modal-active { - visibility: visible; - opacity: 1; -} - -.countries-modal { - position: relative; - background-color: whitesmoke; - width: fit-content; - height: fit-content; - display: flex; - flex-direction: column; - justify-content: space-around; - padding: 2em 4em; - border-radius: 0.3em; - box-shadow: 0 0.2em 0.4em rgba(0, 0, 0, 0.2); - text-align: center; - transition: opacity 0.3s ease; - z-index: 100; -} - -.check-time { - padding: 0.7em 1em; - background-color: rgba(73, 65, 65, 0.842); - color: white; - border: none; - border-radius: 0.3em; - cursor: pointer; -} - -.check-time:hover { - background-color: rgba(112, 108, 108, 0.842); - border: none; -} - -.close-countries-modal { - position: absolute; - color: rgba(73, 65, 65, 0.842); - top: 1em; - right: 1em; - font-weight: bold; -} - -.close-countries-modal:hover { - color: brown; - cursor: pointer; -} - -#countries-datalist { - margin: 1em 0; - padding: 0.7em 1em; - border: 0.1em solid rgba(73, 65, 65, 0.842); - border-radius: 0.3em; -} - -#countries-datalist:hover { - background-color:aliceblue; -} - -.empty-fields-error { - color: brown; -} - -.empty-fields-error-disappear { - display: none; -} - -.results { - color: rgb(9, 65, 65); - margin-bottom: 1em; - font-weight: bold; -} - -.icon-credits, -.icon-credits a:link, -.icon-credits a:visited, -.icon-credits a:hover, -.icon-credits a:active { - color: rgb(202, 202, 202); -} - .shared-list-item-off { display: none; } diff --git a/app/static/js/graph.js b/app/static/graph.js similarity index 88% rename from app/static/js/graph.js rename to app/static/graph.js index b14f881b..0a2b7daf 100644 --- a/app/static/js/graph.js +++ b/app/static/graph.js @@ -1,6 +1,6 @@ function busiestDayOfTheWeekGraph(events) { events = JSON.parse(events); - + const data = Object.values(events); const labels = Object.keys(events); const ctx = document.getElementById("myChart"); @@ -40,12 +40,10 @@ function busiestDayOfTheWeekGraph(events) { function addEventsAfterPageLoaded() { const element = document.getElementsByClassName("graph")[0]; - if (element) { - element.addEventListener("click", function () { - let eventsPerDateData = element.name; - busiestDayOfTheWeekGraph(eventsPerDateData); - }, false); - } + element.addEventListener("click", function() { + let eventsPerDateData = element.name; + busiestDayOfTheWeekGraph(eventsPerDateData); + }, false); } document.addEventListener("DOMContentLoaded", addEventsAfterPageLoaded); \ No newline at end of file diff --git a/app/static/grid_style.css b/app/static/grid_style.css index 026474a0..1afdca10 100644 --- a/app/static/grid_style.css +++ b/app/static/grid_style.css @@ -129,13 +129,6 @@ header div { text-align: end; } -.logo-container { - max-width: 5em; - height: auto; - margin-left: auto; - margin-top: 0.5em; -} - /* Main Element Grid */ main { display: flex; @@ -436,61 +429,8 @@ main { } .dates-calc { - background-color: #222831; - color: white; -} - -/*toggle views*/ -.btn-group-sm>.btn, .btn-sm { - padding: .1rem .3rem !important; - font-size: .7rem !important; -} - -.btn-outline-primary { - border-color: var(--primary) !important; - color: var(--primary) !important; -} - -.btn-outline-primary:hover { - background-color: var(--primary) !important; - color: white !important; -} - -.btn-group>.btn-check:checked+.btn { - background-color: var(--primary) !important; - color: white !important; -} - -.btn-group>.btn-check:focus+.btn { - box-shadow: var(--primary) !important; -} - -#calendarview { - -ms-overflow-style: none; - scrollbar-width: none; -} - -#calendarview::-webkit-scrollbar { - display: none; -} - -.day-view-scrollbar { - overflow-y: scroll; - -ms-overflow-style: none; - scrollbar-width: none; -} - -.day-view-scrollbar::-webkit-scrollbar { - display: none; -} - -.day-view-limitions { - max-width: 30vw; - max-height: 90vh -} - -.transition { - transition-duration: 0.4s; + background-color: #222831; + color: white; } #darkmode { diff --git a/app/static/images/logo/mainlogo.svg b/app/static/images/logo/mainlogo.svg deleted file mode 100644 index 735f44ba..00000000 --- a/app/static/images/logo/mainlogo.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/app/static/images/notes/notes.jfif b/app/static/images/notes/notes.jfif deleted file mode 100644 index d876c78a..00000000 Binary files a/app/static/images/notes/notes.jfif and /dev/null differ diff --git a/app/static/images/worldwide.png b/app/static/images/worldwide.png deleted file mode 100644 index c92d716d..00000000 Binary files a/app/static/images/worldwide.png and /dev/null differ diff --git a/app/static/js/todo_list_modals.js b/app/static/js/todo_list_modals.js deleted file mode 100644 index 33938922..00000000 --- a/app/static/js/todo_list_modals.js +++ /dev/null @@ -1,32 +0,0 @@ -window.addEventListener('DOMContentLoaded', (event) => { - document.getElementsByName('open-edit-btn').forEach(function(entry) { - entry.addEventListener('click', openEditModal); - }); - - document.getElementById('edit-modal-delete').addEventListener( - 'click', deleteModal); -}); - -function openEditModal(event){ - const modal = document.getElementById('edit-modal'); - const button = event.target; - const taskId = button.getAttribute('data-bs-task-id'); - const taskTime = button.getAttribute('data-bs-time'); - const taskTitle = button.getAttribute('data-bs-title'); - const taskDescription = button.getAttribute('data-bs-description'); - const taskImportant = button.getAttribute('data-bs-important') == "true"; - - - document.getElementById('edit-task-id').value = taskId; - document.getElementById('customer-time2').value = taskTime; - document.getElementById('customer-title2').value = taskTitle; - document.getElementById('customer-descrip2').value = taskDescription; - document.getElementById('is-important2').checked = taskImportant; -} - - -function deleteModal() { - const taskId = document.getElementById('edit-task-id').value; - document.getElementById('delete-task-id').value = taskId; - document.getElementById('delete-task-form').submit(); -} \ No newline at end of file diff --git a/app/static/js/view_toggle.js b/app/static/js/view_toggle.js deleted file mode 100644 index 602b6451..00000000 --- a/app/static/js/view_toggle.js +++ /dev/null @@ -1,65 +0,0 @@ -function refreshview() { - location.reload(); -} - -function close_the_view() { - dayviewdiv = document.getElementById("day-view"); - dayviewdiv.classList.remove("day-view-class", "day-view-limitions", "day-view-visible"); - dayviewdiv.innerText = ''; -} - -function set_views_styles(view_element, element_id) { - if (element_id == "day-view") { - view_element.classList.add("day-view-class", "day-view-limitions"); - btn = document.getElementById('close-day-view'); - btn.addEventListener("click", function() { - close_the_view(); - }); - } -} - -function change_view(view, day, element_id) { - if (element_id == "calendarview") { - close_the_view(); - } - const path = `/${view}/${day}`; - fetch(path).then(function(response) { - return response.text(); - }).then(function(html) { - view_element = document.getElementById(element_id); - view_element.innerHTML = html; - set_views_styles(view_element, element_id); - }); -} - -function set_toggle_view_btns(btn, view) { - dayview_btn = document.getElementById(btn); - day = dayview_btn.name; - dayview_btn.addEventListener('click', function() { - change_view(view, day, "calendarview"); - }); -} - -function set_days_view_open() { - const Days = document.getElementsByClassName('day'); - for (let i = 0; i < Days.length; i++) { - let day = Days[i].title; - Days[i].addEventListener('click', function() { - change_view('day', day, 'day-view'); - }); - } -} - - -document.addEventListener( - 'DOMContentLoaded', - function() { - set_toggle_view_btns('week-view-button', 'week'); - set_toggle_view_btns('day-view-button', 'day'); - month = document.getElementById('month-view-button'); - month.addEventListener('click', function() { - refreshview(); - }); - set_days_view_open(); - } -) diff --git a/app/static/notes.css b/app/static/notes.css deleted file mode 100644 index 3970924c..00000000 --- a/app/static/notes.css +++ /dev/null @@ -1,72 +0,0 @@ -* { - padding: 0; - margin: 0; - box-sizing: border-box; -} - -body { - background-color: rgb(219, 226, 226); -} - -.row { - background-color: white; - border-radius: 2rem; - box-shadow: 0.75rem 0.75rem 1.375rem gray; -} - -img { - border-top-left-radius: 2rem; - border-bottom-left-radius: 2rem; -} - -.btn1 { - border: none; - outline: none; - height: 3.125em; - width: 100%; - background-color: black; - color: white; - border-radius: 0.25rem; - font-weight: bold; -} - -.btn1:hover { - background-color: white; - color: black; - border: 1px solid; -} - -.table-wrapper { - padding: 1.25rem 1.5rem; - margin: 1rem 0; -} - -.table-title { - color: #fff; - padding-bottom: 1rem; - background: linear-gradient( - 45deg, - rgba(0, 97, 215, 1) 0%, - rgba(0, 200, 255, 1) 100% - ); - border-top-left-radius: 2rem; - border-top-right-radius: 2rem; - padding: 1rem 2rem; - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.247); -} - -.table-title .btn-group { - float: right; -} - -.table-title .btn { - color: #fff; - float: right; - min-width: 3em; - margin-left: 1rem; - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.247); -} - -table.table tr th:last-child { - width: 3.75em; -} diff --git a/app/static/popover.js b/app/static/popover.js new file mode 100644 index 00000000..ed6f9b8a --- /dev/null +++ b/app/static/popover.js @@ -0,0 +1,9 @@ +// Enable bootstrap popovers + +var popoverList = popoverTriggerList.map(function (popoverTriggerEl) { + return new bootstrap.Popover(popoverTriggerEl, { + container: 'body', + html: true, + sanitize: false + }) +}); diff --git a/app/static/style.css b/app/static/style.css index 170d9999..0d50dd25 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -163,22 +163,13 @@ p { margin-bottom: 1em; } -.forgot-password { - line-height: 0; - color: rgb(188, 7, 194); - padding-left: 8rem; -} - -.red-massage { - color: red; -} - .input-upload-file { margin-top: 1em; } .relative.overflow-hidden { background-color: var(--backgroundcol); + height: 100vh; } .navbar-light .navbar-nav .nav-link { @@ -214,10 +205,3 @@ h2.modal-title { height: 2.5rem; margin-top: 1rem; } - -.reset-password{ - font-size: 2rem; - font-style: bold; - margin: 2rem; - color:black; -} diff --git a/app/static/weekview.css b/app/static/weekview.css index 30ff3e9c..99743ec6 100644 --- a/app/static/weekview.css +++ b/app/static/weekview.css @@ -1,5 +1,13 @@ +:root { + --primary:#30465D; + --primary-variant:#FFDE4D; + --secondary:#EF5454; + --borders:#E7E7E7; + --borders-variant:#F7F7F7; +} + .day-weekview { - border: 1px solid var(--borders); + border-left: 1px solid var(--borders); width: 100%; } @@ -7,14 +15,8 @@ display: grid; grid-template-rows: 1fr; grid-template-columns: 2.3em 1fr; - overflow-y: scroll; - -ms-overflow-style: none; - scrollbar-width: none; } -#week-view::-webkit-scrollbar { - display: none; -} #week-schedule { grid-row: 1; @@ -37,7 +39,3 @@ margin-left: -2; overflow: hidden; } - -#all_day_event_in_week { - color: #EF5454; -} diff --git a/app/templates/agenda.html b/app/templates/agenda.html index 42d4ba47..eda087c2 100644 --- a/app/templates/agenda.html +++ b/app/templates/agenda.html @@ -2,35 +2,35 @@ {% block head %} {{ super() }} - - + + {% endblock %} {% block content %}
-
+
- +
-
+
- +
- +
@@ -73,11 +73,11 @@

{{ start_date.strftime("%d/%m/%Y") }} - {{ end_date.strftime("%d/
{{ events_date }}
{% for event in events_list %} -
- {% set availability = 'Busy' if event.availability else 'Free' %} -
+
+ {% set availability, availability_text = ("busy", gettext("Busy")) if event.availability else ("free", gettext("Free")) %} +
{{ event.start }} - {{ event.title | safe }}
- duration: {{ event.duration }} + {{ gettext("duration: %(duration)s", duration=event.duration) }}
{% endfor %} @@ -85,4 +85,4 @@

{{ start_date.strftime("%d/%m/%Y") }} - {{ end_date.strftime("%d/ {% endfor %}

-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/app/templates/archive.html b/app/templates/archive.html index a74da791..f4038bba 100644 --- a/app/templates/archive.html +++ b/app/templates/archive.html @@ -1,26 +1,14 @@ {% extends "./partials/notification/base.html" %} -{% block page_name %}Archive{% endblock page_name %} +{% block page_name %}{{ gettext("Archived notifications") }}{% endblock page_name %} {% block description %} -
-
Archived Notifications
-

- In this page you can view all of your archived notifications.
- Any notification you have marked as read or declined, you will see here.
- You can use the - button to accept an invitation that you already declined. -

-
+
{{ gettext("Archived notifications") }}
{% endblock description %} -{% block link %} - -{% endblock link %} - {% block notifications %} - {% include './partials/notification/generate_archive.html' %} + {% include "./partials/notification/generate_archive.html" %} {% endblock notifications %} {% block no_notifications_msg %} - You don't have any archived notifications. + {{ gettext("You don't have any archived notifications.") }} {% endblock no_notifications_msg %} diff --git a/app/templates/base.html b/app/templates/base.html index eadfc9c1..c441bdcb 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -8,8 +8,8 @@ - - + + PyLendar {% block title %}{% endblock %} {% endblock %} @@ -25,7 +25,7 @@ @@ -33,51 +33,51 @@
diff --git a/app/templates/partials/user_profile/sidebar_left/features_card.html b/app/templates/partials/user_profile/sidebar_left/features_card.html index 946d1040..6d458484 100644 --- a/app/templates/partials/user_profile/sidebar_left/features_card.html +++ b/app/templates/partials/user_profile/sidebar_left/features_card.html @@ -2,7 +2,7 @@

- Explore more features + {{ gettext("Explore more features") }}

diff --git a/app/templates/partials/user_profile/sidebar_left/features_card/export_calendar.html b/app/templates/partials/user_profile/sidebar_left/features_card/export_calendar.html index 3a34dade..cce36cf6 100644 --- a/app/templates/partials/user_profile/sidebar_left/features_card/export_calendar.html +++ b/app/templates/partials/user_profile/sidebar_left/features_card/export_calendar.html @@ -1,15 +1,15 @@
-
+



- +
diff --git a/app/templates/partials/user_profile/sidebar_left/profile_card.html b/app/templates/partials/user_profile/sidebar_left/profile_card.html index b4dbaec2..67041532 100644 --- a/app/templates/partials/user_profile/sidebar_left/profile_card.html +++ b/app/templates/partials/user_profile/sidebar_left/profile_card.html @@ -1,8 +1,8 @@
- {% include 'partials/user_profile/sidebar_left/profile_card/profile_update_menu.html' %} + {% include "partials/user_profile/sidebar_left/profile_card/profile_update_menu.html" %} - {% include 'partials/user_profile/sidebar_left/profile_card/modals.html' %} + {% include "partials/user_profile/sidebar_left/profile_card/modals.html" %} - {% include 'partials/user_profile/sidebar_left/profile_card/user_details.html' %} -
\ No newline at end of file + {% include "partials/user_profile/sidebar_left/profile_card/user_details.html" %} +
diff --git a/app/templates/partials/user_profile/sidebar_left/profile_card/modals.html b/app/templates/partials/user_profile/sidebar_left/profile_card/modals.html index cde0ca1e..5660f5a0 100644 --- a/app/templates/partials/user_profile/sidebar_left/profile_card/modals.html +++ b/app/templates/partials/user_profile/sidebar_left/profile_card/modals.html @@ -1,17 +1,17 @@ -{% include 'partials/user_profile/sidebar_left/profile_card/modals/name_modal.html' %} +{% include "partials/user_profile/sidebar_left/profile_card/modals/name_modal.html" %} -{% include 'partials/user_profile/sidebar_left/profile_card/modals/email_modal.html' %} +{% include "partials/user_profile/sidebar_left/profile_card/modals/email_modal.html" %} -{% include 'partials/user_profile/sidebar_left/profile_card/modals/description_modal.html' %} +{% include "partials/user_profile/sidebar_left/profile_card/modals/description_modal.html" %} -{% include 'partials/user_profile/sidebar_left/profile_card/modals/upload_photo_modal.html' %} +{% include "partials/user_profile/sidebar_left/profile_card/modals/upload_photo_modal.html" %} -{% include 'partials/user_profile/sidebar_left/profile_card/modals/telegram_modal.html' %} +{% include "partials/user_profile/sidebar_left/profile_card/modals/telegram_modal.html" %} -{% include 'partials/user_profile/sidebar_left/profile_card/modals/calendar_privacy_modal.html' %} \ No newline at end of file +{% include "partials/user_profile/sidebar_left/profile_card/modals/calendar_privacy_modal.html" %} diff --git a/app/templates/partials/user_profile/sidebar_left/profile_card/modals/calendar_privacy_modal.html b/app/templates/partials/user_profile/sidebar_left/profile_card/modals/calendar_privacy_modal.html index 0a2a7e7c..0c7aa828 100644 --- a/app/templates/partials/user_profile/sidebar_left/profile_card/modals/calendar_privacy_modal.html +++ b/app/templates/partials/user_profile/sidebar_left/profile_card/modals/calendar_privacy_modal.html @@ -3,7 +3,7 @@ \ No newline at end of file +

diff --git a/app/templates/partials/user_profile/sidebar_left/profile_card/user_details.html b/app/templates/partials/user_profile/sidebar_left/profile_card/user_details.html index 6cfc749c..094e91b9 100644 --- a/app/templates/partials/user_profile/sidebar_left/profile_card/user_details.html +++ b/app/templates/partials/user_profile/sidebar_left/profile_card/user_details.html @@ -3,12 +3,12 @@
{{ user.full_name }}

- - Settings + + {{ gettext("Settings") }}


{{ user.description }}

-
\ No newline at end of file + diff --git a/app/templates/partials/user_profile/sidebar_right.html b/app/templates/partials/user_profile/sidebar_right.html index c84dedb8..56c4f27c 100644 --- a/app/templates/partials/user_profile/sidebar_right.html +++ b/app/templates/partials/user_profile/sidebar_right.html @@ -1,15 +1,15 @@
- {% include 'partials/user_profile/sidebar_right/meetups.html' %} + {% include "partials/user_profile/sidebar_right/meetups.html" %} - {% include 'partials/user_profile/sidebar_right/on_this_day.html' %} + {% include "partials/user_profile/sidebar_right/on_this_day.html" %} - {% include 'partials/user_profile/sidebar_right/new_card.html' %} + {% include "partials/user_profile/sidebar_right/new_card.html" %} - {% include 'partials/user_profile/sidebar_right/new_card2.html' %} + {% include "partials/user_profile/sidebar_right/new_card.html" %} -
\ No newline at end of file + diff --git a/app/templates/partials/user_profile/sidebar_right/new_card.html b/app/templates/partials/user_profile/sidebar_right/new_card.html index ff6350f7..0fa26275 100644 --- a/app/templates/partials/user_profile/sidebar_right/new_card.html +++ b/app/templates/partials/user_profile/sidebar_right/new_card.html @@ -1,7 +1,7 @@

- {{ gettext("Your Card") }} + {{ gettext("Your card") }}

-
\ No newline at end of file + diff --git a/app/templates/partials/user_profile/sidebar_right/new_card2.html b/app/templates/partials/user_profile/sidebar_right/new_card2.html deleted file mode 100644 index 20529588..00000000 --- a/app/templates/partials/user_profile/sidebar_right/new_card2.html +++ /dev/null @@ -1,7 +0,0 @@ -
-
-

- Your Card -

-
-
\ No newline at end of file diff --git a/app/templates/profile.html b/app/templates/profile.html index a977c24c..a20ec53d 100644 --- a/app/templates/profile.html +++ b/app/templates/profile.html @@ -5,13 +5,13 @@
- {% include 'partials/user_profile/sidebar_left.html' %} + {% include "partials/user_profile/sidebar_left.html" %} - {% include 'partials/user_profile/middle_content.html' %} + {% include "partials/user_profile/middle_content.html" %} - {% include 'partials/user_profile/sidebar_right.html' %} + {% include "partials/user_profile/sidebar_right.html" %}
diff --git a/app/templates/reset_password.html b/app/templates/reset_password.html deleted file mode 100644 index cbfa5fbd..00000000 --- a/app/templates/reset_password.html +++ /dev/null @@ -1,44 +0,0 @@ -{% extends "base.html" %} -{% block content %} -
-
Please choose a new password:
- {% if message %} -
{{ message }}
- {% endif %} -
-
- -
-
-
-
-
- -
-
-
-
- - Must be between 3 to 20 characters. - -
-
-
- -
-
-
-
- - Must be equal to password. - -
-
- -
- -
- -{% endblock %} diff --git a/app/templates/reset_password_mail.html b/app/templates/reset_password_mail.html deleted file mode 100644 index 76b8108e..00000000 --- a/app/templates/reset_password_mail.html +++ /dev/null @@ -1,6 +0,0 @@ -

Hi, {{ recipient }}!

-

You received this email from Calendar because you asked to reset your password.

-

To continue with resetting your password, please click the confirmation link

-

This confirmation link will expired in 15 minutes

-

This email has been sent to {{ email }}.

-

If you did not ask for it, please ignore it.

\ No newline at end of file diff --git a/app/templates/salary/month.j2 b/app/templates/salary/month.j2 index c11e3984..3f180af0 100644 --- a/app/templates/salary/month.j2 +++ b/app/templates/salary/month.j2 @@ -4,35 +4,35 @@

{{ category }}

-

Monthly Salary

+

{{ gettext("Monthly salary") }}

- +
- +
- +
- +
- +
-

Need to alter your settings? Edit your settings here

+

{{ gettext("Edit settings") }}

-{% endblock content %} \ No newline at end of file +{% endblock content %} diff --git a/app/templates/salary/pick.j2 b/app/templates/salary/pick.j2 index 351431a4..bca8d2e3 100644 --- a/app/templates/salary/pick.j2 +++ b/app/templates/salary/pick.j2 @@ -4,15 +4,15 @@

{% if edit %} - Edit Settings + {{ gettext("Edit settings") }} {% else %} - View Salary + {{ gettext("View salary") }} {% endif %}

- + {% for id, category in categories.items() %} {% endfor %}
- + {% endif %}
- - + +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
- +
{% if category %} -

Settings don't need editing? View your salary here

+

{{ gettext("View your salary") }}

{% else %} -

Already created your settings? View your salary or Edit your salary

+

{{ gettext("Setting created?") }} {{ gettext("View your salary") }} {{ gettext("or") }} {{ gettext("Edit your salary") }}

{% endif %}
-{% endblock content %} \ No newline at end of file +{% endblock content %} diff --git a/app/templates/salary/view.j2 b/app/templates/salary/view.j2 index 3fa31b6a..ebd1a0c4 100644 --- a/app/templates/salary/view.j2 +++ b/app/templates/salary/view.j2 @@ -7,38 +7,38 @@

{{ category }}

{{ month }} {{ salary.year}}

- +

Base Salary

-

Hourly Wage: {{ wage.wage }}

-

Number of shifts: {{ salary.num_of_shifts }}

-

Base Salary: {{ salary.base_salary }}

+

{{ gettext("Hourly wage:") }} {{ wage.wage }}

+

{{ gettext("Number of shifts:") }} {{ salary.num_of_shifts }}

+

{{ gettext("Base salary:") }} {{ salary.base_salary }}

Additions

{% if salary.month_weekly_overtime %} -

Weekly Overtime Total: {{ salary.month_weekly_overtime }}

+

Weekly overtime total:") }} {{ salary.month_weekly_overtime }}

{% endif %} {% if salary.transport %} -

Transport: {{ salary.transport }}

+

{{ gettext("Transport:") }} {{ salary.transport }}

{% endif %} {% if salary.bonus %} -

Bonus: {{ salary.bonus }}

+

{{ gettext("Bonus:") }} {{ salary.bonus }}

{% endif %} - + {% if salary.deduction %}

Deductions

-

Deduction: {{ salary.deduction }}

+

{{ gettext("Deduction:") }} {{ salary.deduction }}

{% endif %}
- +
-

Need to alter your settings? Edit your settings here

+

{{ gettext("Edit settings") }}

-{% endblock content %} \ No newline at end of file +{% endblock content %} diff --git a/app/templates/weekview.html b/app/templates/weekview.html index 76c626d4..a23f62de 100644 --- a/app/templates/weekview.html +++ b/app/templates/weekview.html @@ -12,15 +12,12 @@
- {% for day, dayview, events_and_attrs, current_time, all_day_events in week %} + {% for day, dayview, events_and_attr in week %}
-
{{ day.strftime('%a').upper()}} - {% for event in all_day_events %} - {{ event.title }} - {% endfor %} -
+
{{ day.strftime('%A').upper()[:3] }}
{% set month = day.month %} {% set day = day.day %} + {% set events = events_and_attr%} {% include dayview.template %}
{% endfor %} @@ -38,4 +35,4 @@
- + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 9612912e..694f6209 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,7 +21,6 @@ bcrypt==3.2.0 beautifulsoup4==4.9.3 black==20.8b1 blinker==1.4 -bs4==0.0.1 cachetools==4.2.0 certifi==2020.12.5 cffi==1.14.4 @@ -77,7 +76,6 @@ lazy-object-proxy==1.5.2 loguru==0.5.3 makefun==1.9.5 MarkupSafe==1.1.1 -maxminddb==2.0.3 mccabe==0.6.1 mocker==1.1.1 multidict==5.1.0 @@ -117,9 +115,6 @@ pytest-mock==3.5.1 pytest-xdist==2.2.0 python-dateutil==2.8.1 python-dotenv==0.15.0 -python-geoip==1.2 -python-geoip-geolite2==2015.303 -python-geoip-python3==1.3 python-multipart==0.0.5 pytz==2020.5 PyYAML==5.3.1 @@ -143,6 +138,7 @@ text-unidecode==1.3 text2emotion==0.0.5 textblob==0.15.3 toml==0.10.2 +tox==3.21.4 tqdm==4.56.0 trio==0.18.0 typed-ast==1.4.2 diff --git a/tests/fixtures/dayview_fixture.py b/tests/fixtures/dayview_fixture.py index 4ebef7a7..769651a3 100644 --- a/tests/fixtures/dayview_fixture.py +++ b/tests/fixtures/dayview_fixture.py @@ -9,112 +9,63 @@ def event1(): start = datetime(year=2021, month=2, day=1, hour=7, minute=5) end = datetime(year=2021, month=2, day=1, hour=9, minute=15) - return Event( - title="test1", - content="test", - start=start, - end=end, - owner_id=1, - ) + return Event(title='test1', content='test', + start=start, end=end, owner_id=1) @pytest.fixture def event2(): start = datetime(year=2021, month=2, day=1, hour=13, minute=13) end = datetime(year=2021, month=2, day=1, hour=15, minute=46) - return Event( - title="test2", - content="test", - start=start, - end=end, - owner_id=1, - color="blue", - ) + return Event(title='test2', content='test', + start=start, end=end, owner_id=1, color='blue') @pytest.fixture def event3(): start = datetime(year=2021, month=2, day=3, hour=7, minute=5) end = datetime(year=2021, month=2, day=3, hour=9, minute=15) - return Event( - title="test3", - content="test", - start=start, - end=end, - owner_id=1, - ) + return Event(title='test3', content='test', + start=start, end=end, owner_id=1) @pytest.fixture def all_day_event1(): start = datetime(year=2021, month=2, day=3, hour=7, minute=5) end = datetime(year=2021, month=2, day=3, hour=9, minute=15) - return Event( - title="test3", - content="test", - all_day=True, - start=start, - end=end, - owner_id=1, - ) + return Event(title='test3', content='test', all_day=True, + start=start, end=end, owner_id=1) @pytest.fixture def small_event(): start = datetime(year=2021, month=2, day=3, hour=7) end = datetime(year=2021, month=2, day=3, hour=8, minute=30) - return Event( - title="test3", - content="test", - start=start, - end=end, - owner_id=1, - ) + return Event(title='test3', content='test', + start=start, end=end, owner_id=1) @pytest.fixture def event_with_no_minutes_modified(): start = datetime(year=2021, month=2, day=3, hour=7) end = datetime(year=2021, month=2, day=3, hour=8) - return Event( - title="test_no_modify", - content="test", - start=start, - end=end, - owner_id=1, - ) + return Event(title='test_no_modify', content='test', + start=start, end=end, owner_id=1) @pytest.fixture def multiday_event(): start = datetime(year=2021, month=2, day=1, hour=13) end = datetime(year=2021, month=2, day=3, hour=13) - return Event( - title="test_multiday", - content="test", - start=start, - end=end, - owner_id=1, - color="blue", - ) - - -@pytest.fixture -def not_today(): - date = datetime(year=2012, month=12, day=12, hour=12, minute=12) - return date + return Event(title='test_multiday', content='test', + start=start, end=end, owner_id=1, color='blue') @pytest.fixture def weekdays(): return [ - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday", + 'Sunday', 'Monday', 'Tuesday', + 'Wednesday', 'Thursday', 'Friday', 'Saturday', ] diff --git a/tests/notes/__init__.py b/tests/notes/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/notes/test_notes.py b/tests/notes/test_notes.py deleted file mode 100644 index ee73009a..00000000 --- a/tests/notes/test_notes.py +++ /dev/null @@ -1,186 +0,0 @@ -import json -import pytest -from starlette.testclient import TestClient - -from app.internal.notes import notes - - -def test_create_note(notes_test_client: TestClient, monkeypatch): - dummy_user = { - "username": "new_user", - "email": "my@email.po", - "full_name": "My Name", - "language_id": 1, - "description": "Happy new user!", - "target_weight": None, - "id": 1, - "is_active": False, - } - test_request_payload = { - "title": "something", - "description": "something else", - } - test_response_payload = { - "id": 1, - "title": "something", - "description": "something else", - "timestamp": None, - "creator": dummy_user, - } - - async def mock_post(session, payload): - return 1 - - monkeypatch.setattr(notes, "create", mock_post) - - response = notes_test_client.post( - "/notes/", data=json.dumps(test_request_payload) - ) - - assert response.status_code == 201 - assert response.json() == test_response_payload - - -def test_create_note_invalid_json(notes_test_client: TestClient): - response = notes_test_client.post( - "/notes/", data=json.dumps({"titles": "something"}) - ) - assert response.status_code == 422 - - -def test_read_note(notes_test_client: TestClient, monkeypatch): - test_data = { - "id": 1, - "title": "something", - "description": "something else", - "timestamp": "2021-02-15T07:13:55.837950", - "creator": None, - } - - async def mock_get(session, id): - return test_data - - monkeypatch.setattr(notes, "view", mock_get) - - response = notes_test_client.get("/notes/1") - assert response.status_code == 200 - assert response.json() == test_data - - -def test_read_note_incorrect_id(notes_test_client: TestClient, monkeypatch): - async def mock_get(session, id): - return None - - monkeypatch.setattr(notes, "view", mock_get) - - response = notes_test_client.get("/notes/666") - assert response.status_code == 404 - assert response.json()["detail"] == "Note with id 666 not found" - - -def test_read_all_notes(notes_test_client: TestClient, monkeypatch): - test_data = [ - { - "id": 1, - "title": "something", - "description": "something else", - "timestamp": "2021-02-15T07:13:55.837950", - }, - { - "id": 2, - "title": "someone", - "description": "someone else", - "timestamp": "2021-02-15T07:13:55.837950", - }, - ] - - async def mock_get_all(session): - return test_data - - monkeypatch.setattr(notes, "get_all", mock_get_all) - - response = notes_test_client.get("/notes/") - assert response.status_code == 200 - assert response.context["data"] == test_data - - -def test_update_note(notes_test_client: TestClient, monkeypatch): - test_update_data = { - "id": 1, - "title": "someone", - "description": "someone else", - "timestamp": "2021-02-15T07:13:55.837950", - } - - async def mock_get(session, id): - return True - - monkeypatch.setattr(notes, "view", mock_get) - - async def mock_put(session, id, payload): - return test_update_data - - monkeypatch.setattr(notes, "update", mock_put) - - response = notes_test_client.put( - "/notes/1/", data=json.dumps(test_update_data) - ) - assert response.status_code == 202 - assert response.json() == test_update_data - - -@pytest.mark.parametrize( - "id, payload, status_code", - [ - [1, {}, 422], - [1, {"description": "bar"}, 422], - [999, {"title": "foo", "description": "bar"}, 404], - ], -) -def test_update_note_invalid( - notes_test_client: TestClient, monkeypatch, id, payload, status_code -): - async def mock_get(session, id): - return None - - monkeypatch.setattr(notes, "view", mock_get) - - response = notes_test_client.put( - f"/notes/{id}/", - data=json.dumps(payload), - ) - assert response.status_code == status_code - - -def test_delete_note(notes_test_client: TestClient, monkeypatch): - test_data = { - "id": 1, - "title": "something", - "description": "something else", - "timestamp": "2021-02-15T07:13:55.837950", - } - - async def mock_get(session, id): - return test_data - - monkeypatch.setattr(notes, "view", mock_get) - - async def mock_delete(session, id): - return test_data - - monkeypatch.setattr(notes, "delete", mock_delete) - - response = notes_test_client.delete("/notes/1/") - assert response.status_code == 200 - assert response.json() == test_data - - -def test_delete_note_incorrect_id(notes_test_client: TestClient, monkeypatch): - async def mock_get(session, id): - return None - - monkeypatch.setattr(notes, "view", mock_get) - - response = notes_test_client.delete("/notes/999/") - assert response.status_code == 404 - assert response.json()["detail"] == "Note with id 999 not found" diff --git a/tests/notes/test_notes_routes.py b/tests/notes/test_notes_routes.py deleted file mode 100644 index 6317c622..00000000 --- a/tests/notes/test_notes_routes.py +++ /dev/null @@ -1,13 +0,0 @@ -def test_notes_page(client): - response = client.get("/notes") - assert response.ok - - -def test_add_note_page(client): - response = client.get("/notes/add") - assert response.ok - - -def test_notes_api(client): - response = client.get("/notes/all") - assert response.ok diff --git a/tests/salary/conftest.py b/tests/salary/conftest.py index 51c77288..f61472d7 100644 --- a/tests/salary/conftest.py +++ b/tests/salary/conftest.py @@ -10,12 +10,12 @@ from tests.conftest import get_test_db, test_engine MESSAGES = { - "create_settings": "Already created your settings?", - "pick_settings": "Edit Settings", - "edit_settings": "Settings don't need editing?", - "pick_category": "View Salary", - "view_salary": "Need to alter your settings?", - "salary_calc": "Total Salary:", + "create_settings": "Setting created?", + "pick_settings": "Edit settings", + "edit_settings": "View your salary", + "pick_category": "View salary", + "view_salary": "Edit settings", + "salary_calc": "Total salary:", } ROUTES = { diff --git a/tests/test_audio.py b/tests/test_audio.py index f7040b9b..c304f203 100644 --- a/tests/test_audio.py +++ b/tests/test_audio.py @@ -8,7 +8,7 @@ def test_get_settings(audio_test_client): response = audio_test_client.get(url=AUDIO_SETTINGS_URL) assert response.ok - assert b"Audio Settings" in response.content + assert b"Audio settings" in response.content def test_start_audio_default(audio_test_client): diff --git a/tests/test_calendar_grid.py b/tests/test_calendar_grid.py index e5442b02..66ef8287 100644 --- a/tests/test_calendar_grid.py +++ b/tests/test_calendar_grid.py @@ -5,13 +5,12 @@ DATE = datetime.date(1988, 5, 3) DAY = cg.Day(datetime.date(1988, 5, 3)) WEEKEND = cg.DayWeekend(datetime.date(2021, 1, 23)) -SUNDAY = cg.Day(datetime.date(2021, 1, 3)) N_DAYS = 3 N_DAYS_BEFORE = datetime.date(1988, 4, 30) NEXT_N_DAYS = [ cg.Day(datetime.date(1988, 5, 4)), cg.Day(datetime.date(1988, 5, 5)), - cg.Day(datetime.date(1988, 5, 6)), + cg.Day(datetime.date(1988, 5, 6)) ] DAY_TYPES = [cg.Day, cg.DayWeekend, cg.Today, cg.FirstDayMonth] WEEK_DAYS = cg.Week.WEEK_DAYS @@ -28,7 +27,7 @@ def test_get_calendar(client): def test_get_calendar_extends(client): days = 42 response = client.get( - f"/calendar/month/add/{DAY.set_id()}?days={days}", + f"/calendar/month/add/{DAY.set_id()}?days={days}" ) assert response.ok assert b"08-May" in response.content @@ -36,10 +35,10 @@ def test_get_calendar_extends(client): @staticmethod def test_create_day(): dates_to_check = { - "normal_day": datetime.date(2021, 1, 20), - "weekend": datetime.date(2021, 1, 23), - "today": datetime.date.today(), - "first_month": datetime.date(2021, 1, 1), + 'normal_day': datetime.date(2021, 1, 20), + 'weekend': datetime.date(2021, 1, 23), + 'today': datetime.date.today(), + 'first_month': datetime.date(2021, 1, 1) } for i, value in enumerate(dates_to_check.values()): @@ -60,8 +59,9 @@ def test_get_date_before_n_days(): @staticmethod def test_get_first_day_month_block(Calendar): - assert cg.get_first_day_month_block(DATE) == next( - Calendar.itermonthdates(DATE.year, DATE.month), + assert ( + cg.get_first_day_month_block(DATE) + == next(Calendar.itermonthdates(DATE.year, DATE.month)) ) @staticmethod @@ -81,9 +81,7 @@ def test_create_weeks(): @staticmethod def test_get_month_block(Calendar): month_weeks = cg.create_weeks( - Calendar.itermonthdates(1988, 5), - WEEK_DAYS, - ) + Calendar.itermonthdates(1988, 5), WEEK_DAYS) get_block = cg.get_month_block(cg.Day(DATE), n=len(month_weeks)) for i in range(len(month_weeks)): @@ -96,9 +94,8 @@ def test_get_user_local_time(): server_time = cg.Day.get_user_local_time() server_time_check = datetime.datetime.today() assert server_time - assert server_time.strftime(time_string) == server_time_check.strftime( - time_string, - ) + assert server_time.strftime( + time_string) == server_time_check.strftime(time_string) @staticmethod def test_is_weekend(): @@ -107,28 +104,16 @@ def test_is_weekend(): @staticmethod def test_display_day(): - assert DAY.display() == "03 MAY 88" + assert DAY.display() == '03 MAY 88' @staticmethod def test_set_id(): - assert DAY.set_id() == "03-May-1988" + assert DAY.set_id() == '03-May-1988' @staticmethod def test_display_str(): - assert str(DAY) == "03" + assert str(DAY) == '03' @staticmethod def test_create_week_object(): assert cg.Week(NEXT_N_DAYS) - - @staticmethod - def test_dayview_format(): - assert DAY.dayview_format() == "1988-05-03" - - @staticmethod - def test_weekview_format(): - assert DAY.weekview_format() == "1988-05-01" - - @staticmethod - def test_weekview_format_on_sunday(): - assert SUNDAY.weekview_format() == "2021-01-03" diff --git a/tests/test_calendar_privacy.py b/tests/test_calendar_privacy.py index ccc42545..0fde3d0a 100644 --- a/tests/test_calendar_privacy.py +++ b/tests/test_calendar_privacy.py @@ -2,7 +2,7 @@ # TODO after user system is merged: # from app.internal.security.dependancies import CurrentUser -from app.internal.user.user import _create_user +from app.routers.register import _create_user def test_can_show_calendar_public(session, user): diff --git a/tests/test_credits.py b/tests/test_credits.py index feb620e3..3ddfdd53 100644 --- a/tests/test_credits.py +++ b/tests/test_credits.py @@ -1,5 +1,5 @@ class TestCredits: - CREDITS_OPENING = b"Meet The Team" + CREDITS_OPENING = b"Say hello to our developers" @staticmethod def test_get_credits_ok_request(client): diff --git a/tests/test_dayview.py b/tests/test_dayview.py index e0fe4a27..ef77eb05 100644 --- a/tests/test_dayview.py +++ b/tests/test_dayview.py @@ -3,26 +3,14 @@ import pytest from bs4 import BeautifulSoup -from app.database.models import Event, User +from app.database.models import Event from app.routers.dayview import ( - CurrentTimeAttributes, - EventsAttributes, + DivAttributes, is_all_day_event_in_day, is_specific_time_event_in_day, ) from app.routers.event import create_event -REGISTER_DETAIL = { - "username": "correct_user", - "full_name": "full_name", - "password": "correct_password", - "confirm_password": "correct_password", - "email": "example@email.com", - "description": "", -} - -LOGIN_DATA = {"username": "correct_user", "password": "correct_password"} - def create_dayview_event(events, session, user): for event in events: @@ -37,34 +25,21 @@ def create_dayview_event(events, session, user): def test_minutes_position_calculation(event_with_no_minutes_modified): - div_attr = EventsAttributes(event_with_no_minutes_modified) + div_attr = DivAttributes(event_with_no_minutes_modified) assert div_attr._minutes_position(div_attr.start_time.minute) is None assert div_attr._minutes_position(div_attr.end_time.minute) is None assert div_attr._minutes_position(0) is None - assert div_attr._minutes_position(60)["min_position"] == 4 + assert div_attr._minutes_position(60) == 4 def test_div_attributes(event1): - div_attr = EventsAttributes(event1) + div_attr = DivAttributes(event1) assert div_attr.total_time == "07:05 - 09:15" assert div_attr.grid_position == "32 / 40" assert div_attr.length == 130 assert div_attr.color == "grey" -def test_current_time_gets_today_attributes(): - today = datetime.now() - current_attr = CurrentTimeAttributes(today) - assert current_attr.dayview_date == today.date() - assert current_attr.is_viewed is True - - -def test_current_time_gets_not_today_attributes(not_today): - current_attr = CurrentTimeAttributes(not_today) - assert str(current_attr.dayview_date) == "2012-12-12" - assert current_attr.is_viewed is False - - @pytest.mark.parametrize( "minutes,css_class,visiblity", [ @@ -83,18 +58,18 @@ def test_font_size_attribute(minutes, css_class, visiblity): end=end, owner_id=1, ) - div_attr = EventsAttributes(event) + div_attr = DivAttributes(event) assert div_attr.title_size_class == css_class assert div_attr.total_time_visible == visiblity def test_div_attr_multiday(multiday_event): day = datetime(year=2021, month=2, day=1) - assert EventsAttributes(multiday_event, day).grid_position == "55 / 101" + assert DivAttributes(multiday_event, day).grid_position == "55 / 101" day += timedelta(hours=24) - assert EventsAttributes(multiday_event, day).grid_position == "1 / 101" + assert DivAttributes(multiday_event, day).grid_position == "1 / 101" day += timedelta(hours=24) - assert EventsAttributes(multiday_event, day).grid_position == "1 / 55" + assert DivAttributes(multiday_event, day).grid_position == "1 / 55" def test_is_specific_time_event_in_day(all_day_event1, event3): @@ -132,41 +107,19 @@ def test_is_all_day_event_in_day(all_day_event1, event3): def test_div_attributes_with_costume_color(event2): - div_attr = EventsAttributes(event2) + div_attr = DivAttributes(event2) assert div_attr.color == "blue" -def test_needs_login(session, dayview_test_client): - response = dayview_test_client.get("/day/2021-2-1") - assert response.ok - assert b"Login" in response.content - - -def test_wrong_timeformat(session, dayview_test_client): - dayview_test_client.post( - dayview_test_client.app.url_path_for("register"), - data=REGISTER_DETAIL, - ) - dayview_test_client.post( - dayview_test_client.app.url_path_for("login"), - data=LOGIN_DATA, - ) - response = dayview_test_client.get("/day/1-2-2021") +def test_wrong_timeformat(session, user, client, event1, event2, event3): + create_dayview_event([event1, event2, event3], session=session, user=user) + response = client.get("/day/1-2-2021") assert response.status_code == 404 -def test_dayview_html(event1, event2, event3, session, dayview_test_client): - dayview_test_client.post( - dayview_test_client.app.url_path_for("register"), - data=REGISTER_DETAIL, - ) - dayview_test_client.post( - dayview_test_client.app.url_path_for("login"), - data=LOGIN_DATA, - ) - user = session.query(User).filter_by(username="correct_user").first() +def test_dayview_html(event1, event2, event3, session, user, client): create_dayview_event([event1, event2, event3], session=session, user=user) - response = dayview_test_client.get("/day/2021-2-1") + response = client.get("/day/2021-2-1") soup = BeautifulSoup(response.content, "html.parser") assert "FEBRUARY" in str(soup.find("div", {"id": "top-tab"})) assert "event1" in str(soup.find("div", {"id": "event1"})) @@ -185,22 +138,14 @@ def test_dayview_html(event1, event2, event3, session, dayview_test_client): def test_dayview_html_with_multiday_event( multiday_event, session, - dayview_test_client, + user, + client, day, grid_position, ): - dayview_test_client.post( - dayview_test_client.app.url_path_for("register"), - data=REGISTER_DETAIL, - ) - dayview_test_client.post( - dayview_test_client.app.url_path_for("login"), - data=LOGIN_DATA, - ) - user = session.query(User).filter_by(username="correct_user").first() create_dayview_event([multiday_event], session=session, user=user) session.commit() - response = dayview_test_client.get(f"/day/{day}") + response = client.get(f"/day/{day}") soup = BeautifulSoup(response.content, "html.parser") grid_pos = f"grid-row: {grid_position};" assert grid_pos in str(soup.find("div", {"id": "event1"})) diff --git a/tests/test_event.py b/tests/test_event.py index 597a5b7b..c9b69172 100644 --- a/tests/test_event.py +++ b/tests/test_event.py @@ -13,7 +13,6 @@ from app.config import PICTURE_EXTENSION from app.database.models import Comment, Event from app.dependencies import UPLOAD_PATH, get_db -from app.internal.event import add_countries_to_db, get_all_countries_names from app.internal.privacy import PrivacyKinds from app.internal.utils import delete_instance from app.main import app @@ -120,14 +119,6 @@ "is_google_event": "False", } -CONVERT_TIME_FORM_DATA = { - "countries": "France, Paris", - "start_date": "2021-02-02", - "start_time": "14:00", - "end_date": "2021-02-02", - "end_time": "15:00", -} - CORRECT_ADD_EVENT_DATA = { "title": "test", "start": "2021-02-13T09:03:49.560Z", @@ -150,17 +141,6 @@ {"end": datetime(1990, 1, 1)}, ] -REGISTER_DETAIL = { - "username": "correct_user", - "full_name": "full_name", - "password": "correct_password", - "confirm_password": "correct_password", - "email": "example@email.com", - "description": "", -} - -LOGIN_DATA = {"username": "correct_user", "password": "correct_password"} - def test_get_events(event_test_client, session, event): response = event_test_client.get("/event/") @@ -175,43 +155,12 @@ def test_create_event_api(event_test_client, session, event): assert response.ok -def test_eventedit_without_user(event_test_client, session): - response = event_test_client.get("/event/edit") - assert response.ok - assert b"Login" in response.content - - -def test_eventedit_with_user(event_test_client, session): - event_test_client.post( - event_test_client.app.url_path_for("register"), - data=REGISTER_DETAIL, - ) - event_test_client.post( - event_test_client.app.url_path_for("login"), - data=LOGIN_DATA, - ) +def test_eventedit(event_test_client): response = event_test_client.get("/event/edit") assert response.ok assert b"Event Details" in response.content -def test_get_all_countries(session): - assert get_all_countries_names(session) - - -def test_db_returns_countries_matching_timezone(event_test_client, session): - add_countries_to_db(session) - country_name = "Israel, Jerusalem" - response = event_test_client.get( - event_test_client.app.url_path_for( - "check_timezone", - country_name=country_name, - ), - ) - assert response.ok - assert b'{"timezone":"Asia/Jerusalem"}' in response.content - - def test_eventview_with_id(event_test_client, session, event): event_id = event.id response = event_test_client.get(f"/event/{event_id}") @@ -728,7 +677,7 @@ def test_delete_comment( } response = event_test_client.post(path, data=data, allow_redirects=True) assert response.ok - assert "Post Comment" in response.text + assert "Post comment" in response.text assert session.query(Comment).first() is None diff --git a/tests/test_export.py b/tests/test_export.py index 9c246790..7eb40151 100644 --- a/tests/test_export.py +++ b/tests/test_export.py @@ -5,26 +5,23 @@ from app.config import ICAL_VERSION, PRODUCT_ID from app.internal import export from app.internal.agenda_events import filter_dates -from app.internal.user import user +from app.routers.user import get_all_user_events class TestExport: + @staticmethod def test_export(client, sender, today_event): - response = client.get("/export?start_date=&end_date=") + response = client.get('/export?start_date=&end_date=') assert isinstance(response.content, bytes) assert response.ok @staticmethod def test_get_events_no_time_frame( - today_event, - next_month_event, - sender, - session, - ): + today_event, next_month_event, sender, session): start = None end = None - events = user.get_all_user_events(session, sender.id) + events = get_all_user_events(session, sender.id) filtered_events = filter_dates(events, start, end) assert set(filtered_events) == {today_event, next_month_event} @@ -32,93 +29,74 @@ def test_get_events_no_time_frame( def test_get_events_end_time_frame_success(today_event, sender, session): start = None end = today_event.end.date() - events = user.get_all_user_events(session, sender.id) + events = get_all_user_events(session, sender.id) filtered_events = filter_dates(events, start, end) assert list(filtered_events) == [today_event] @staticmethod def test_get_events_end_time_frame_failure( - today_event, - next_month_event, - sender, - session, - ): + today_event, next_month_event, sender, session): start = None end = today_event.start.date() - timedelta(days=1) - events = user.get_all_user_events(session, sender.id) + events = get_all_user_events(session, sender.id) filtered_events = filter_dates(events, start, end) assert list(filtered_events) == [] @staticmethod def test_get_events_start_time_frame_success( - today_event, - next_month_event, - sender, - session, - ): + today_event, next_month_event, sender, session): start = next_month_event.start.date() end = None - events = user.get_all_user_events(session, sender.id) + events = get_all_user_events(session, sender.id) filtered_events = filter_dates(events, start, end) assert list(filtered_events) == [next_month_event] @staticmethod def test_get_events_start_time_frame_failure( - today_event, - next_month_event, - sender, - session, - ): + today_event, next_month_event, sender, session): start = next_month_event.start.date() + timedelta(days=1) end = None - events = user.get_all_user_events(session, sender.id) + events = get_all_user_events(session, sender.id) filtered_events = filter_dates(events, start, end) assert list(filtered_events) == [] @staticmethod def test_get_events_start_and_end_time_frame_success( - today_event, - next_month_event, - sender, - session, - ): + today_event, next_month_event, sender, session): start = today_event.start.date() end = next_month_event.start.date() - events = user.get_all_user_events(session, sender.id) + events = get_all_user_events(session, sender.id) filtered_events = filter_dates(events, start, end) assert set(filtered_events) == {today_event, next_month_event} @staticmethod def test_get_events_start_and_end_time_frame_failure( - today_event, - next_month_event, - sender, - session, - ): + today_event, next_month_event, sender, session): start = today_event.start.date() + timedelta(days=1) end = next_month_event.start.date() - timedelta(days=1) - events = user.get_all_user_events(session, sender.id) + events = get_all_user_events(session, sender.id) filtered_events = filter_dates(events, start, end) assert list(filtered_events) == [] class TestExportInternal: + @staticmethod def test_create_icalendar(): icalendar = export._create_icalendar() - assert icalendar.get("version") == ICAL_VERSION - assert icalendar.get("prodid") == PRODUCT_ID + assert icalendar.get('version') == ICAL_VERSION + assert icalendar.get('prodid') == PRODUCT_ID @staticmethod def test_create_icalendar_event(event): ievent = export._create_icalendar_event(event) - assert event.owner.email in ievent.get("organizer") - assert ievent.get("summary") == event.title + assert event.owner.email in ievent.get('organizer') + assert ievent.get('summary') == event.title @staticmethod def test_get_v_cal_address(event, user): email = "test_email" v_cal_address = export._get_v_cal_address(email) - test_v_cal_address = vCalAddress(f"MAILTO:{email}") + test_v_cal_address = vCalAddress(f'MAILTO:{email}') assert v_cal_address == test_v_cal_address @staticmethod @@ -127,7 +105,7 @@ def test_get_icalendar(user, event): def does_contain(item: str) -> bool: """Returns True if calendar contains item.""" - return bytes(item, encoding="utf8") in bytes(icalendar) + return bytes(item, encoding='utf8') in bytes(icalendar) assert does_contain(ICAL_VERSION) assert does_contain(PRODUCT_ID) @@ -136,14 +114,8 @@ def does_contain(item: str) -> bool: @staticmethod def test_get_icalendar_with_multiple_events( - session, - user, - event, - today_event, - ): + session, user, event, today_event): icalendar = export.get_icalendar_with_multiple_events( - session, - [event, today_event], - ) + session, [event, today_event]) assert icalendar diff --git a/tests/test_feature_panel.py b/tests/test_feature_panel.py deleted file mode 100644 index 1ee57b7c..00000000 --- a/tests/test_feature_panel.py +++ /dev/null @@ -1,216 +0,0 @@ -import pytest - -from app.database.models import Feature, UserFeature -import app.internal.features as internal -import app.routers.features as route -from tests.test_login import LOGIN_DATA, REGISTER_DETAIL - - -@pytest.fixture -def mock_features(): - return [ - { - "name": "test", - "route": "/test", - "description": "testing", - "creator": "test", - }, - ] - - -@pytest.fixture -@pytest.mark.usefixtures("session") -def feature(session): - test = Feature( - name="test", - route="/test", - description="testing", - creator="test", - ) - - session.add(test) - session.commit() - - yield test - - session.query(Feature).delete() - session.commit() - - -@pytest.fixture -@pytest.mark.usefixtures("session") -def association_off(session, user): - test = UserFeature(feature_id=1, user_id=user.id, is_enable=False) - - session.add(test) - session.commit() - - yield test - - session.query(UserFeature).delete() - session.commit() - - -@pytest.fixture -@pytest.mark.usefixtures("session") -def association_on(session, user): - test = UserFeature(feature_id=1, user_id=user.id, is_enable=True) - - session.add(test) - session.commit() - - yield test - - session.query(UserFeature).delete() - session.commit() - - -@pytest.fixture -def update_dict(): - update = { - "name": "test", - "route": "/route-test", - "description": "update", - "creator": "test", - } - - return update - - -@pytest.fixture -def form_mock(): - form = {"feature_id": 1, "user_id": 1} - - return form - - -def test_create_features_at_startup(mocker, session, mock_features): - mocker.patch("app.internal.features.features", mock_features) - mocker.patch("app.internal.features.is_feature_exists", return_value=False) - - assert internal.create_features_at_startup(session) - - -def test_create_association(mocker, session, user, feature): - assert ( - internal.create_user_feature_association( - db=session, - feature_id=1, - user_id=user.id, - is_enable=False, - ) - is not None - ) - - -def test_get_user_enabled_features(session, feature, association_on, user): - assert ( - internal.get_user_installed_features(session=session, user_id=user.id)[ - 0 - ] - is not None - ) - - -def test_is_user_has_feature(session, feature, association_off, user): - assert internal.is_user_has_feature( - session=session, - user_id=user.id, - feature_id=feature.id, - ) - - -def test_delete_feature(session, feature): - feat = session.query(Feature).filter_by(name=feature.name).first() - internal.delete_feature(feature=feat, session=session) - feat = session.query(Feature).filter_by(name=feature.name).first() - assert feat is None - - -def test_is_feature_exist_in_db(session, feature): - assert internal.is_feature_exists({ - 'name': 'test', - 'route': '/test' - }, session) - - -def test_update_feature(session, feature, update_dict): - feature = internal.update_feature(feature, update_dict, session) - assert feature.description == "update" - - -@pytest.mark.asyncio -async def test_is_feature_enabled(mocker, session, association_on, user): - mocker.patch("app.internal.features.SessionLocal", return_value=session) - mocker.patch( - "app.internal.features.get_authorization_cookie", - return_value=None, - ) - mocker.patch( - "app.internal.features.current_user", - return_value=user, - ) - - assert ( - await internal.is_access_allowd(route="/route", request=None) is True - ) - - -def test_create_feature(session): - feat = internal.create_feature( - name="test1", - route="/route", - description="testing", - creator="test", - db=session, - ) - assert feat.name == "test1" - - -def test_index(security_test_client): - url = route.router.url_path_for("index") - - resp = security_test_client.get(url) - assert resp.ok - - -def test_add_feature_to_user(form_mock, security_test_client): - url = route.router.url_path_for("add_feature_to_user") - - security_test_client.post( - security_test_client.app.url_path_for("register"), - data=REGISTER_DETAIL, - ) - security_test_client.post( - security_test_client.app.url_path_for("login"), - data=LOGIN_DATA, - ) - - resp = security_test_client.post(url, data=form_mock) - assert resp.ok - - -def test_delete_user_feature_association( - form_mock, - feature, - security_test_client, -): - - security_test_client.post( - security_test_client.app.url_path_for("register"), - data=REGISTER_DETAIL, - ) - security_test_client.post( - security_test_client.app.url_path_for("login"), - data=LOGIN_DATA, - ) - security_test_client.post( - route.router.url_path_for("add_feature_to_user"), - data=form_mock, - ) - - url = route.router.url_path_for("delete_user_feature_association") - - resp = security_test_client.post(url, data=form_mock) - assert resp.ok - assert resp.content == b"true" diff --git a/tests/test_google_connect.py b/tests/test_google_connect.py index ab91ae14..cfb1f466 100644 --- a/tests/test_google_connect.py +++ b/tests/test_google_connect.py @@ -8,8 +8,8 @@ import app.internal.google_connect as google_connect from app.database.models import OAuthCredentials -from app.internal.user.user import _create_user from app.routers.event import create_event +from app.routers.register import _create_user @pytest.fixture diff --git a/tests/test_jinja_variable.py b/tests/test_jinja_variable.py deleted file mode 100644 index 76dbc642..00000000 --- a/tests/test_jinja_variable.py +++ /dev/null @@ -1,32 +0,0 @@ -REGISTER_DETAIL = { - "username": "correct_user", - "full_name": "full_name", - "password": "correct_password", - "confirm_password": "correct_password", - "email": "example@email.com", - "description": "", -} - -LOGIN_DATA = {"username": "correct_user", "password": "correct_password"} - - -def test_user_not_logged_in(session, security_test_client): - security_test_client.get(security_test_client.app.url_path_for("logout")) - response = security_test_client.get("/about") - assert b"Sign Out" not in response.content - assert b"Sign In" in response.content - - -def test_user_is_logged_in(session, security_test_client): - security_test_client.get(security_test_client.app.url_path_for("logout")) - security_test_client.post( - security_test_client.app.url_path_for("register"), - data=REGISTER_DETAIL, - ) - security_test_client.post( - security_test_client.app.url_path_for("login"), - data=LOGIN_DATA, - ) - response = security_test_client.get("/about") - assert b"Sign Out" in response.content - assert b"Sign In" not in response.content diff --git a/tests/test_login.py b/tests/test_login.py index 7b49bb05..f11eb0f6 100644 --- a/tests/test_login.py +++ b/tests/test_login.py @@ -30,7 +30,6 @@ def test_login_route_ok(security_test_client): ] LOGIN_DATA = {"username": "correct_user", "password": "correct_password"} - WRONG_LOGIN_DATA = { "username": "incorrect_user", "password": "correct_password", diff --git a/tests/test_register.py b/tests/test_register.py index a9b619ad..9394e2ba 100644 --- a/tests/test_register.py +++ b/tests/test_register.py @@ -8,191 +8,83 @@ def test_register_route_ok(client): REGISTER_FORM_VALIDATORS = [ - ( - "ad", - "admin_user", - "password", - "password", - "example@mail.com", - "description", - b"Username must contain", - ), - ( - "admin", - "admin_user", - "pa", - "pa", - "example@mail.com", - "description", - b"Password must contain", - ), - ( - "admin", - "admin_user", - "password", - "wrong_password", - "example@mail.com", - "description", - b"match", - ), - ( - "admin", - "admin_user", - "password", - "password", - "invalid_mail", - "description", - b"Email address is not valid", - ), - ( - "", - "admin_user", - "password", - "password", - "example@mail.com", - "description", - b"Username field is required", - ), - ( - "admin", - "", - "password", - "password", - "example@mail.com", - "description", - b"Full_name field is required", - ), - ( - "admin", - "admin_user", - "", - "password", - "example@mail.com", - "description", - b"Password field is required", - ), - ( - "admin", - "admin_user", - "password", - "", - "example@mail.com", - "description", - b"Confirm_password field is required", - ), - ( - "admin", - "admin_user", - "password", - "password", - "", - "description", - b"Email field is required", - ), - ( - "@admin", - "admin_user", - "password", - "password", - "example@mail.com", - "description", - b"can not start with", - ), -] + ('ad', 'admin_user', 'password', 'password', 'example@mail.com', + 'description', b'Username must contain'), + ('admin', 'admin_user', 'pa', 'pa', 'example@mail.com', + 'description', b'Password must contain'), + ('admin', 'admin_user', 'password', 'wrong_password', 'example@mail.com', + 'description', b"match"), + ('admin', 'admin_user', 'password', 'password', 'invalid_mail', + 'description', b"Email address is not valid"), + ('', 'admin_user', 'password', 'password', 'example@mail.com', + 'description', b'Username field is required'), + ('admin', '', 'password', 'password', 'example@mail.com', + 'description', b'Full_name field is required'), + ('admin', 'admin_user', '', 'password', 'example@mail.com', + 'description', b'Password field is required'), + ('admin', 'admin_user', 'password', '', 'example@mail.com', + 'description', b'Confirm_password field is required'), + ('admin', 'admin_user', 'password', 'password', '', + 'description', b'Email field is required'), + ] + + +""" +Test all active pydantic validators +""" @pytest.mark.parametrize( "username, full_name, password, confirm_password, email, description," - + "expected_response", - REGISTER_FORM_VALIDATORS, -) + + "expected_response", REGISTER_FORM_VALIDATORS) def test_register_form_validators( - client, - username, - full_name, - password, - confirm_password, - email, - description, - expected_response, -): + client, username, full_name, password, confirm_password, + email, description, expected_response): data = { - "username": username, - "full_name": full_name, - "password": password, - "confirm_password": confirm_password, - "email": email, - "description": description, - } - data = client.post("/register", data=data).content + 'username': username, 'full_name': full_name, + 'password': password, 'confirm_password': confirm_password, + 'email': email, 'description': description} + data = client.post('/register', data=data).content assert expected_response in data +""" +Test successfully register user to database, after passing all validators +""" + + def test_register_successfull(session, security_test_client): - data = { - "username": "username", - "full_name": "full_name", - "password": "password", - "confirm_password": "password", - "email": "example@email.com", - "description": "", - } - """ - Test successfully register user to database, after passing all validators - """ - data = security_test_client.post("/register", data=data) + data = {'username': 'username', 'full_name': 'full_name', + 'password': 'password', 'confirm_password': 'password', + 'email': 'example@email.com', 'description': ""} + data = security_test_client.post('/register', data=data) assert data.status_code == HTTP_302_FOUND UNIQUE_FIELDS_ARE_TAKEN = [ - ( - "admin", - "admin_user", - "password", - "password", - "example_new@mail.com", - "description", - b"That username is already taken", - ), - ( - "admin_new", - "admin_user", - "password", - "password", - "example@mail.com", - "description", - b"Email already registered", - ), -] + ('admin', 'admin_user', 'password', 'password', 'example_new@mail.com', + 'description', b'That username is already taken'), + ('admin_new', 'admin_user', 'password', 'password', 'example@mail.com', + 'description', b"Email already registered") + ] + + +""" +Test register a user fails due to unique database fields already in use +""" @pytest.mark.parametrize( "username, full_name, password, confirm_password," - + "email, description, expected_response", - UNIQUE_FIELDS_ARE_TAKEN, -) + + "email, description, expected_response", UNIQUE_FIELDS_ARE_TAKEN) def test_unique_fields_are_taken( - session, - security_test_client, - username, - full_name, - password, - confirm_password, - email, - description, - expected_response, -): - """ - Test register a user fails due to unique database fields already in use - """ + session, security_test_client, username, + full_name, password, confirm_password, + email, description, expected_response): user_data = { - "username": "username", - "full_name": "full_name", - "password": "password", - "confirm_password": "password", - "email": "example@email.com", - "description": "", - } - security_test_client.post("/register", data=user_data) - data = security_test_client.post("/register", data=user_data).content + 'username': 'username', 'full_name': 'full_name', + 'password': 'password', 'confirm_password': 'password', + 'email': 'example@email.com', 'description': ""} + security_test_client.post('/register', data=user_data) + data = security_test_client.post('/register', data=user_data).content assert expected_response in data diff --git a/tests/test_reset_password.py b/tests/test_reset_password.py deleted file mode 100644 index 02890ba7..00000000 --- a/tests/test_reset_password.py +++ /dev/null @@ -1,205 +0,0 @@ -import pytest - -from starlette.status import HTTP_302_FOUND - -from app.internal.security.ouath2 import create_jwt_token -from app.internal.email import mail -from app.internal.security.schema import ForgotPassword - - -REGISTER_DETAIL = { - "username": "correct_user", - "full_name": "full_name", - "password": "correct_password", - "confirm_password": "correct_password", - "email": "example@email.com", - "description": "", -} - -FORGOT_PASSWORD_BAD_DETAILS = [ - ("", ""), - ("", "example@email.com"), - ("correct_user", ""), - ("incorrect_user", "example@email.com"), - ("correct_user", "inncorrect@email.com"), -] - -FORGOT_PASSWORD_DETAILS = { - "username": "correct_user", - "email": "example@email.com", -} - -RESET_PASSWORD_BAD_CREDENTIALS = [ - ("", "", ""), - ("correct_user", "", "new_password"), - ("", "new_password", "new_password"), - ("correct_user", "new_password", ""), - ("wrong_user", "new_password", "new_password"), - ("correct_user", "", "new_password"), - ("correct_user", "new_password", ""), - ("correct_user", "new_password", "new_password1"), -] - -RESET_PASSWORD_DETAILS = { - "username": "correct_user", - "password": "new_password", - "confirm-password": "new_password", -} - - -def test_forgot_password_route_ok(security_test_client): - response = security_test_client.get( - security_test_client.app.url_path_for("forgot_password_form"), - ) - assert response.ok - - -@pytest.mark.parametrize("username, email", FORGOT_PASSWORD_BAD_DETAILS) -def test_forgot_password_bad_details( - session, - security_test_client, - username, - email, -): - security_test_client.post("/register", data=REGISTER_DETAIL) - data = {"username": username, "email": email} - res = security_test_client.post( - security_test_client.app.url_path_for("forgot_password"), - data=data, - ) - assert b"Please check your credentials" in res.content - - -def test_email_send(session, security_test_client, smtpd): - security_test_client.post("/register", data=REGISTER_DETAIL) - mail.config.SUPPRESS_SEND = 1 - mail.config.MAIL_SERVER = smtpd.hostname - mail.config.MAIL_PORT = smtpd.port - mail.config.USE_CREDENTIALS = False - mail.config.MAIL_TLS = False - with mail.record_messages() as outbox: - response = security_test_client.post( - security_test_client.app.url_path_for("forgot_password"), - data=FORGOT_PASSWORD_DETAILS, - ) - assert len(outbox) == 1 - assert b"Email for reseting password was sent" in response.content - assert "reset password" in outbox[0]["subject"] - - -def test_reset_password_GET_without_token(session, security_test_client): - res = security_test_client.get( - security_test_client.app.url_path_for("reset_password_form"), - ) - assert b"Verification token is missing" in res.content - - -def test_reset_password_GET_with_token(session, security_test_client): - user = ForgotPassword(**FORGOT_PASSWORD_DETAILS) - token = create_jwt_token(user, jwt_min_exp=15) - params = f"?email_verification_token={token}" - res = security_test_client.get( - security_test_client.app.url_path_for("reset_password_form") - + f"{params}", - ) - assert b"Please choose a new password" in res.content - - -@pytest.mark.parametrize( - "username, password, confirm_password", - RESET_PASSWORD_BAD_CREDENTIALS, -) -def test_reset_password_bad_details( - session, - security_test_client, - username, - password, - confirm_password, -): - security_test_client.post("/register", data=REGISTER_DETAIL) - user = ForgotPassword(**FORGOT_PASSWORD_DETAILS) - token = create_jwt_token(user, jwt_min_exp=15) - data = { - "username": username, - "password": password, - "confirm_password": confirm_password, - } - params = f"?email_verification_token={token}" - res = security_test_client.post( - security_test_client.app.url_path_for("reset_password") + f"{params}", - data=data, - ) - assert b"Please check your credentials" in res.content - - -def test_reset_password_successfully(session, security_test_client): - security_test_client.post("/register", data=REGISTER_DETAIL) - user = ForgotPassword(**FORGOT_PASSWORD_DETAILS) - token = create_jwt_token(user, jwt_min_exp=15) - params = f"?email_verification_token={token}" - res = security_test_client.post( - security_test_client.app.url_path_for("reset_password") + f"{params}", - data=RESET_PASSWORD_DETAILS, - ) - print(res.content) - assert res.status_code == HTTP_302_FOUND - - -def test_reset_password_expired_token(session, security_test_client): - security_test_client.post("/register", data=REGISTER_DETAIL) - user = ForgotPassword(**FORGOT_PASSWORD_DETAILS) - token = create_jwt_token(user, jwt_min_exp=-1) - params = f"?email_verification_token={token}" - res = security_test_client.post( - security_test_client.app.url_path_for("reset_password") + f"{params}", - data=RESET_PASSWORD_DETAILS, - ) - assert res.ok - - -LOGIN_DATA = {"username": "correct_user", "password": "correct_password"} - - -def test_is_logged_in_with_reset_password_token(session, security_test_client): - security_test_client.post("/register", data=REGISTER_DETAIL) - user = ForgotPassword(**FORGOT_PASSWORD_DETAILS) - token = create_jwt_token(user, jwt_min_exp=15) - params = f"?existing_jwt={token}" - security_test_client.post( - security_test_client.app.url_path_for("login") + f"{params}", - data=LOGIN_DATA, - ) - res = security_test_client.get( - security_test_client.app.url_path_for("is_logged_in"), - ) - assert b"Your token is not valid" in res.content - - -def test_is_manager_with_reset_password_token(session, security_test_client): - security_test_client.post("/register", data=REGISTER_DETAIL) - user = ForgotPassword(**FORGOT_PASSWORD_DETAILS) - token = create_jwt_token(user, jwt_min_exp=15) - params = f"?existing_jwt={token}" - security_test_client.post( - security_test_client.app.url_path_for("login") + f"{params}", - data=LOGIN_DATA, - ) - res = security_test_client.get( - security_test_client.app.url_path_for("is_manager"), - ) - assert b"have a permition to enter this page" in res.content - - -def test_current_user_with_reset_password_token(session, security_test_client): - security_test_client.post("/register", data=REGISTER_DETAIL) - user = ForgotPassword(**FORGOT_PASSWORD_DETAILS) - token = create_jwt_token(user, jwt_min_exp=15) - params = f"?existing_jwt={token}" - security_test_client.post( - security_test_client.app.url_path_for("login") + f"{params}", - data=LOGIN_DATA, - ) - res = security_test_client.get( - security_test_client.app.url_path_for("current_user"), - ) - assert b"Your token is not valid" in res.content diff --git a/tests/test_statistics.py b/tests/test_statistics.py index e7b85f24..707d571a 100644 --- a/tests/test_statistics.py +++ b/tests/test_statistics.py @@ -7,8 +7,8 @@ SUCCESS_STATUS, get_statistics, ) -from app.internal.user.user import _create_user from app.routers.event import create_event +from app.routers.register import _create_user from app.routers.share import send_in_app_invitation diff --git a/tests/test_todo_list.py b/tests/test_todo_list.py deleted file mode 100644 index ac1089c3..00000000 --- a/tests/test_todo_list.py +++ /dev/null @@ -1,130 +0,0 @@ -import json -from datetime import date, time - -import pytest -from fastapi import status -from sqlalchemy.orm.exc import NoResultFound - -from app.database.models import Task -from app.internal.todo_list import by_id -from app.internal.utils import create_model, delete_instance -from app.routers.todo_list import router - -DATE = date(2021, 2, 2) -TIME = time(20, 0) -TIME2 = time(21, 0) - - -@pytest.fixture -def task1(session, user): - task_example = create_model( - session, - Task, - title="Test", - description="test my create", - is_done=False, - is_important=False, - date=DATE, - time=TIME, - owner_id=1, - owner=user, - ) - yield task_example - delete_instance(session, task_example) - - -@pytest.fixture -def task2(session, user): - task_example = create_model( - session, - Task, - title="Test2", - description="test my create2", - is_done=False, - is_important=True, - date=DATE, - time=TIME2, - owner_id=1, - owner=user, - ) - yield task_example - delete_instance(session, task_example) - - -def test_if_task_deleted(home_test_client, task1: Task, session): - response = home_test_client.post( - router.url_path_for('delete_task'), - data={"task_id": task1.id}, - ) - assert response.status_code == status.HTTP_302_FOUND - with pytest.raises(NoResultFound): - by_id(session, task1.id) - - -def test_if_task_created(home_test_client, session, task1: Task, user): - response = home_test_client.post( - router.url_path_for('add_task'), - data={ - "title": task1.title, - "description": task1.description, - "date_str": task1.date.strftime('%Y-%m-%d'), - "time_str": task1.time.strftime('%H:%M'), - "is_important": task1.is_important, - "session": session - }, - ) - assert response.status_code == status.HTTP_303_SEE_OTHER - task_ex = by_id(session, task1.id) - assert task1 == task_ex - - -def test_if_task_edited(home_test_client, session, task1: Task, task2): - response = home_test_client.post( - router.url_path_for('edit_task'), - data={ - "task_id": task1.id, - "title": task2.title, - "description": task2.description, - "date_str": task2.date.strftime('%Y-%m-%d'), - "time_str": task2.time.strftime('%H:%M:%S'), - "is_important": task2.is_important, - "session": session - }, - ) - assert response.status_code == status.HTTP_303_SEE_OTHER - session.refresh(task1) - assert task2.title == task1.title - assert task2.description == task1.description - assert task2.time == task1.time - assert task2.is_important == task1.is_important - - -def test_if_task_has_done(home_test_client, session, task1: Task): - response = home_test_client.post( - router.url_path_for('set_task_done', task_id=task1.id), - data={"task_id": task1.id}, - ) - assert response.status_code == status.HTTP_303_SEE_OTHER - response = home_test_client.get( - router.url_path_for('get_task', task_id=task1.id), - ) - content = response.content.decode('utf-8') - content = json.loads(content) - assert content['is_done'] is True - - -def test_if_task_has_not_done(home_test_client, session, task1: Task): - response = home_test_client.post( - router.url_path_for('set_task_undone', task_id=task1.id), - data={ - "task_id": task1.id, - "session": session - }, - ) - assert response.status_code == status.HTTP_303_SEE_OTHER - response = home_test_client.get( - router.url_path_for('get_task', task_id=task1.id), - ) - content = response.content.decode('utf-8') - content = json.loads(content) - assert content['is_done'] is False diff --git a/tests/test_user.py b/tests/test_user.py index c7158558..b2dc545c 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -4,9 +4,10 @@ from app.database.models import Event, UserEvent from app.internal.user.availability import disable, enable -from app.internal.user.user import _create_user, does_user_exist, get_users from app.internal.utils import save from app.routers.event import create_event +from app.routers.register import _create_user +from app.routers.user import does_user_exist, get_users @pytest.fixture diff --git a/tests/test_weekview.py b/tests/test_weekview.py index bbd1b46e..a1d40a04 100644 --- a/tests/test_weekview.py +++ b/tests/test_weekview.py @@ -1,21 +1,9 @@ import pytest from bs4 import BeautifulSoup -from app.database.models import User from app.routers.event import create_event from app.routers.weekview import get_week_dates -REGISTER_DETAIL = { - "username": "correct_user", - "full_name": "full_name", - "password": "correct_password", - "confirm_password": "correct_password", - "email": "example@email.com", - "description": "", -} - -LOGIN_DATA = {"username": "correct_user", "password": "correct_password"} - def create_weekview_event(events, session, user): for event in events: @@ -35,32 +23,16 @@ def test_get_week_dates(weekdays, sunday): assert week_dates[i].strftime("%A") == weekdays[i] -def test_weekview_day_names(session, user, weekview_test_client, weekdays): - weekview_test_client.post( - weekview_test_client.app.url_path_for("register"), - data=REGISTER_DETAIL, - ) - weekview_test_client.post( - weekview_test_client.app.url_path_for("login"), - data=LOGIN_DATA, - ) - response = weekview_test_client.get("/week/2021-1-3") +def test_weekview_day_names(session, user, client, weekdays): + response = client.get("/week/2021-1-3") soup = BeautifulSoup(response.content, "html.parser") day_divs = soup.find_all("div", {"class": "day-name"}) for i in range(6): assert weekdays[i][:3].upper() in str(day_divs[i]) -def test_weekview_day_dates(session, weekview_test_client, sunday): - weekview_test_client.post( - weekview_test_client.app.url_path_for("register"), - data=REGISTER_DETAIL, - ) - weekview_test_client.post( - weekview_test_client.app.url_path_for("login"), - data=LOGIN_DATA, - ) - response = weekview_test_client.get("/week/2021-1-3") +def test_weekview_day_dates(session, user, client, sunday): + response = client.get("/week/2021-1-3") soup = BeautifulSoup(response.content, "html.parser") day_divs = soup.find_all("span", {"class": "date-nums"}) week_dates = list(get_week_dates(sunday)) @@ -78,20 +50,12 @@ def test_weekview_html_events( event2, event3, session, - weekview_test_client, + user, + client, date, event, ): - weekview_test_client.post( - weekview_test_client.app.url_path_for("register"), - data=REGISTER_DETAIL, - ) - weekview_test_client.post( - weekview_test_client.app.url_path_for("login"), - data=LOGIN_DATA, - ) - user = session.query(User).filter_by(username="correct_user").first() create_weekview_event([event1, event2, event3], session=session, user=user) - response = weekview_test_client.get(f"/week/{date}") + response = client.get(f"/week/{date}") soup = BeautifulSoup(response.content, "html.parser") assert event in str(soup.find("div", {"id": event}))