-
Notifications
You must be signed in to change notification settings - Fork 52
Feature/import to calendar #119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
yammesicka
merged 27 commits into
PythonFreeCourse:develop
from
Nir-P:feature/import_to_calendar
Feb 1, 2021
+187,818
−0
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
eff5b49
feat: import file to calendar
Nir-P 521625b
feat: import file to calendar
Nir-P 7d8816b
fix: import ics fix invalid date
Nir-P 87f3ded
fix: import ics fix invalid date
Nir-P 6758901
fix: line to long E501
Nir-P ed5704d
fix: change functions to support start and end dates plus code readab…
Nir-P 3bd5f6f
fix: support start and end dates
Nir-P 387c4a4
fix: remove unnecessary test files
Nir-P fd9d0b4
added files from develop
Nir-P 8f0fa28
fix: added globals to confix and some minor changes
Nir-P 36d3765
added from develop and fix requirements
Nir-P b1c7a99
fix: conftest
Nir-P 7939c5e
fix: up coverage tests to 100%
Nir-P 12719b9
fix: up coverage tests to 100% and removed dup fixture
Nir-P 5142ab5
fix: some minor fixes and more readibility
Nir-P 826417f
fix: some minor fixes and more readibility
Nir-P 1b045bd
fix: requirements fix
Nir-P cb35056
fix: small readibility fix
Nir-P fcf0ad6
fix: small readibility
Nir-P a90f824
fix: small changes
Nir-P 9bc8b51
fix: requirements
Nir-P 208ef69
fix: using [] literals
Nir-P 71b8ff5
fix: requirements
Nir-P 8775ae3
fix: using [] literals
Nir-P 3de6b39
fix: small changes
Nir-P 7afb542
fix: small changes
Nir-P 3923d2d
fix: small changes
Nir-P File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,200 @@ | ||
| from collections import defaultdict | ||
| import datetime | ||
| import os | ||
| from pathlib import Path | ||
| import re | ||
| from typing import Any, Dict, Generator, List, Tuple, Union | ||
|
|
||
| from icalendar import Calendar | ||
|
|
||
| from app.config import ( | ||
| EVENT_CONTENT_LIMIT, | ||
| EVENT_HEADER_LIMIT, | ||
| EVENT_HEADER_NOT_EMPTY, | ||
| EVENT_VALID_YEARS, | ||
| MAX_EVENTS_START_DATE, | ||
| MAX_FILE_SIZE_MB, | ||
| VALID_FILE_EXTENSION | ||
| ) | ||
| from app.database.database import SessionLocal | ||
| from app.routers.event import create_event | ||
| from loguru import logger | ||
|
|
||
|
|
||
| DATE_FORMAT = "%m-%d-%Y" | ||
| DESC_EVENT = "VEVENT" | ||
| EVENT_PATTERN = re.compile(r"^(\w{" + str(EVENT_HEADER_NOT_EMPTY) + "," + | ||
| str(EVENT_HEADER_LIMIT) + r"}),\s(\w{0," + | ||
| str(EVENT_CONTENT_LIMIT) + | ||
| r"}),\s(\d{2}-\d{2}-\d{4})," + | ||
| r"\s(\d{2}-\d{2}-\d{4})$") | ||
|
|
||
|
|
||
| def is_file_size_valid(file: str, max_size: int = MAX_FILE_SIZE_MB) -> bool: | ||
| file_size = os.stat(file).st_size / 1048576 # convert bytes to MB. | ||
| return file_size <= max_size | ||
|
|
||
|
|
||
| def is_file_extension_valid(file: str, | ||
yammesicka marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| extension: Union[str, Tuple[str, ...]] | ||
| = VALID_FILE_EXTENSION) -> bool: | ||
| return file.lower().endswith(extension) | ||
|
|
||
|
|
||
| def is_file_exist(file: str) -> bool: | ||
| return Path(file).is_file() | ||
|
|
||
|
|
||
| def is_date_in_range(date: Union[str, datetime.datetime], | ||
| valid_dates: int = EVENT_VALID_YEARS) -> bool: | ||
| """ | ||
| check if date is valid and in the range according to the rule we have set | ||
| """ | ||
| now_year = datetime.datetime.now().year | ||
| if isinstance(date, str): | ||
| try: | ||
| check_date = datetime.datetime.strptime(date, DATE_FORMAT) | ||
| except ValueError: | ||
| return False | ||
| else: | ||
| check_date = date | ||
| return now_year - valid_dates < check_date.year < now_year + valid_dates | ||
|
|
||
|
|
||
| def is_event_text_valid(row: str) -> bool: | ||
| """Check if the row contains valid data""" | ||
| get_values = EVENT_PATTERN.search(row) | ||
| return get_values is not None | ||
|
|
||
|
|
||
| def is_file_valid_to_import(file: str) -> bool: | ||
| """ | ||
| checking before importing that the file exist, the file extension and | ||
| the size meet the rules we have set. | ||
| """ | ||
| return (is_file_exist(file) and is_file_extension_valid(file) and | ||
| is_file_size_valid(file)) | ||
|
|
||
|
|
||
| def is_file_valid_to_save_to_database(events: List[Dict[str, Union[str, Any]]], | ||
| max_event_start_date: int | ||
| = MAX_EVENTS_START_DATE) -> bool: | ||
| """ | ||
| checking after importing that there is no larger quantity of events | ||
| with the same date according to the rule we have set. | ||
| """ | ||
| same_date_counter = 1 | ||
| date_n_count = defaultdict(int) | ||
| for event in events: | ||
| date_n_count[event["S_Date"]] += 1 | ||
| if date_n_count[event["S_Date"]] > same_date_counter: | ||
| same_date_counter = date_n_count[event["S_Date"]] | ||
| return same_date_counter <= max_event_start_date | ||
|
|
||
|
|
||
| def open_txt_file(txt_file: str) -> Generator[str, None, None]: | ||
| with open(txt_file, "r") as text: | ||
| for row in text.readlines(): | ||
| yield row | ||
|
|
||
|
|
||
| def save_calendar_content_txt(event: str, calendar_content: List) -> bool: | ||
| """populate calendar with event content""" | ||
| head, content, start_date, end_date = event.split(", ") | ||
| if (not is_date_in_range(start_date) or | ||
| not is_date_in_range(end_date.replace("\n", ""))): | ||
| return False | ||
| start_date = datetime.datetime.strptime(start_date, DATE_FORMAT) | ||
| end_date = datetime.datetime.strptime(end_date.replace("\n", ""), | ||
| DATE_FORMAT) | ||
| calendar_content.append({"Head": head, | ||
| "Content": content, | ||
| "S_Date": start_date, | ||
| "E_Date": end_date}) | ||
| return True | ||
|
|
||
|
|
||
| def import_txt_file(txt_file: str) -> List[Dict[str, Union[str, Any]]]: | ||
| calendar_content = [] | ||
| for event in open_txt_file(txt_file): | ||
| if (not is_event_text_valid(event) or | ||
| not save_calendar_content_txt(event, calendar_content)): | ||
| return [] | ||
| return calendar_content | ||
|
|
||
|
|
||
| def open_ics(ics_file: str) -> Union[List, Calendar]: | ||
| with open(ics_file, "r") as ics: | ||
| try: | ||
| calendar_read = Calendar.from_ical(ics.read()) | ||
| except (IndexError, ValueError) as e: | ||
| logger.error(f"open_ics function failed error message: {e}") | ||
| return [] | ||
Nir-P marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return calendar_read | ||
|
|
||
|
|
||
| def is_valid_data_event_ics(component) -> bool: | ||
| """check if ics event data content is valid""" | ||
| return not (str(component.get('summary')) is None or | ||
| component.get('dtstart') is None or | ||
| component.get('dtend') is None or | ||
| not is_date_in_range(component.get('dtstart').dt) or | ||
| not is_date_in_range(component.get('dtend').dt)) | ||
|
|
||
|
|
||
| def save_calendar_content_ics(component, calendar_content) -> None: | ||
| calendar_content.append({ | ||
| "Head": str(component.get('summary')), | ||
| "Content": str(component.get('description')), | ||
| "S_Date": component.get('dtstart').dt | ||
| .replace(tzinfo=None), | ||
| "E_Date": component.get('dtend').dt | ||
| .replace(tzinfo=None) | ||
| }) | ||
|
|
||
|
|
||
| def import_ics_file(ics_file: str) -> List[Dict[str, Union[str, Any]]]: | ||
| calendar_content = [] | ||
| calendar_read = open_ics(ics_file) | ||
| if not calendar_read: | ||
| return [] | ||
| for component in calendar_read.walk(): | ||
| if component.name == DESC_EVENT: | ||
| if not is_valid_data_event_ics(component): | ||
| return [] | ||
| save_calendar_content_ics(component, calendar_content) | ||
| return calendar_content | ||
|
|
||
|
|
||
| def save_events_to_database(events: List[Dict[str, Union[str, Any]]], | ||
| user_id: int, | ||
| session: SessionLocal) -> None: | ||
| """insert the events into Event table""" | ||
| for event in events: | ||
| title = event["Head"] | ||
| content = event["Content"] | ||
| start = event["S_Date"] | ||
| end = event["E_Date"] | ||
| owner_id = user_id | ||
| create_event(db=session, | ||
| title=title, | ||
| content=content, | ||
| start=start, | ||
| end=end, | ||
| owner_id=owner_id) | ||
|
|
||
|
|
||
| def user_click_import(file: str, user_id: int, session: SessionLocal) -> bool: | ||
| """ | ||
| when user choose a file and click import, we are checking the file | ||
| and if everything is ok we will insert the data to DB | ||
| """ | ||
| if is_file_valid_to_import(file): | ||
| if is_file_extension_valid(file, ".ics"): | ||
| import_file = import_ics_file(file) | ||
| else: | ||
| import_file = import_txt_file(file) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the function for txt and csv files?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, import_txt_file(file) handles txt and csv files |
||
| if import_file and is_file_valid_to_save_to_database(import_file): | ||
| save_events_to_database(import_file, user_id, session) | ||
| return True | ||
| return False | ||
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| BEGIN:VCALENDAR | ||
| VERSION:2.0 | ||
| CALSCALE:GREGORIAN | ||
| BEGIN:VEVENT | ||
| SUMMARY:HeadA | ||
| DTSTART;TZID=America/New_York:20190802T103400 | ||
| DTEND;TZID=America/New_York:20190802T110400 | ||
| LOCATION:1000 Broadway Ave.\, Brooklyn | ||
| DESCRIPTION:Content1 | ||
| STATUS:CONFIRMED | ||
| SEQUENCE:3 | ||
| BEGIN:VALARM | ||
| TRIGGER:-PT10M | ||
| DESCRIPTION:desc_1 | ||
| ACTION:DISPLAY | ||
| END:VALARM | ||
| END:VEVENT | ||
| BEGIN:VEVENT | ||
| SUMMARY:HeadB | ||
| DTSTART;TZID=America/New_York:20190802T200000 | ||
| DTEND;TZID=America/New_York:20190802T203000 | ||
| LOCATION:900 Jay St.\, Brooklyn | ||
| DESCRIPTION:Content2 | ||
| STATUS:CONFIRMED | ||
| SEQUENCE:3 | ||
| BEGIN:VALARM | ||
| TRIGGER:-PT10M | ||
| DESCRIPTION:desc_2 | ||
| ACTION:DISPLAY | ||
| END:VALARM | ||
| END:VEVENT | ||
| END:VCALENDAR |
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| BEGIN:VCALENDAR | ||
| VERSION:2.0 | ||
| CALSCALE:GREGORIAN | ||
|
|
||
| SUMMARY:HeadA | ||
| DTSTART;TZID=America/New_York:20190802T103400 | ||
| DTEND;TZID=America/New_York:20190802T110400 | ||
| LOCATION:1000 Broadway Ave.\, Brooklyn | ||
| DESCRIPTION:Content1 | ||
| STATUS:CONFIRMED | ||
| SEQUENCE:3 | ||
| BEGIN:VALARM | ||
| TRIGGER:-PT10M | ||
| DESCRIPTION:desc_1 | ||
| ACTION:DISPLAY | ||
| END:VALARM | ||
| END:VEVENT | ||
| BEGIN:VEVENT | ||
| SUMMARY:HeadB | ||
| DTSTART;TZID=America/New_York:20190802T200000 | ||
| DTEND;TZID=America/New_York:20190802T203000 | ||
| LOCATION:900 Jay St.\, Brooklyn | ||
| DESCRIPTION:Content2 | ||
| STATUS:CONFIRMED | ||
| SEQUENCE:3 | ||
| BEGIN:VALARM | ||
| TRIGGER:-PT10M | ||
| DESCRIPTION:desc_2 | ||
| ACTION:DISPLAY | ||
| END:VALARM | ||
| END:VEVENT | ||
| END:VCALENDAR |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| BEGIN:VCALENDAR | ||
| VERSION:2.0 | ||
| CALSCALE:GREGORIAN | ||
| BEGIN:VEVENT | ||
|
|
||
|
|
||
|
|
||
| LOCATION:1000 Broadway Ave.\, Brooklyn | ||
| DESCRIPTION:Content1 | ||
| STATUS:CONFIRMED | ||
| SEQUENCE:3 | ||
| BEGIN:VALARM | ||
| TRIGGER:-PT10M | ||
| DESCRIPTION:desc_1 | ||
| ACTION:DISPLAY | ||
| END:VALARM | ||
| END:VEVENT | ||
| BEGIN:VEVENT | ||
| SUMMARY:HeadB | ||
| DTSTART;TZID=America/New_York:20190802T200000 | ||
| DTEND;TZID=America/New_York:20190802T203000 | ||
| LOCATION:900 Jay St.\, Brooklyn | ||
| DESCRIPTION:Content2 | ||
| STATUS:CONFIRMED | ||
| SEQUENCE:3 | ||
| BEGIN:VALARM | ||
| TRIGGER:-PT10M | ||
| DESCRIPTION:desc_2 | ||
| ACTION:DISPLAY | ||
| END:VALARM | ||
| END:VEVENT | ||
| END:VCALENDAR |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.