-
Notifications
You must be signed in to change notification settings - Fork 0
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
7.create user and user mangment #3
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,26 @@ | ||
# jpydzr6-monkeys | ||
# jpydzr6-monkeys | ||
|
||
Aplikacja do zarządzania budżetem domowym. | ||
Dane zapisywane są w bazie danych SQLite. | ||
Możliwość rejestracji i logowania użytkownika. | ||
Możliwość dodawnia wpływów i wydatków z możliwością edycji i usuwania | ||
Budowanie raportów. | ||
|
||
|
||
Environment: | ||
- Python 3.12.4 | ||
- Peewee 3.16.1 | ||
- SQLite 3.44.0 | ||
|
||
To start the application, run the following command: | ||
``` | ||
python main.py | ||
|
||
``` | ||
|
||
To run the test script user_manager.py, run the following command: | ||
|
||
``` | ||
python test_user_manager.py | ||
|
||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# Komunikaty dotyczące hasła, żeby było bezpiecznie :) | ||
PASSWORD_REQUIREMENTS = """ | ||
Wymagania dotyczące hasła: | ||
• Minimum 8 znaków | ||
• Przynajmniej jedna wielka litera (A-Z) | ||
• Przynajmniej jedna mała litera (a-z) | ||
• Przynajmniej jedna cyfra (0-9) | ||
• Przynajmniej jeden znak specjalny (!@#$%^&*()_+-=[]{}|;:,.<>?) | ||
|
||
Przykład dobrego hasła: 'Admin@2024!' | ||
""" | ||
|
||
# Komunikaty błędów hasła | ||
PASSWORD_TOO_SHORT = f""" | ||
❌ Hasło jest za krótkie! | ||
{PASSWORD_REQUIREMENTS}""" | ||
|
||
PASSWORD_NO_UPPERCASE = f""" | ||
❌ Brak wielkiej litery! | ||
{PASSWORD_REQUIREMENTS}""" | ||
|
||
PASSWORD_NO_LOWERCASE = f""" | ||
❌ Brak małej litery! | ||
{PASSWORD_REQUIREMENTS}""" | ||
|
||
PASSWORD_NO_DIGIT = f""" | ||
❌ Brak cyfry! | ||
{PASSWORD_REQUIREMENTS}""" | ||
|
||
PASSWORD_NO_SPECIAL = f""" | ||
❌ Brak znaku specjalnego! | ||
{PASSWORD_REQUIREMENTS}""" | ||
|
||
PASSWORD_OK = "✅ Hasło spełnia wszystkie wymagania!" | ||
|
||
# Komunikaty dotyczące emaila | ||
EMAIL_INVALID = "❌ Nieprawidłowy format emaila" | ||
EMAIL_TOO_SHORT = "❌ Email jest za krótki" | ||
EMAIL_NO_DOMAIN = "❌ Nieprawidłowa domena" | ||
EMAIL_OK = "✅ Email jest poprawny" | ||
|
||
# Menu i inne komunikaty używane przy pliku test_user_manager.py | ||
MAIN_MENU = """ | ||
1. Dodaj użytkownika | ||
2. Usuń użytkownika | ||
3. Edytuj użytkownika | ||
4. Pokaż użytkowników | ||
5. Zaloguj się | ||
0. Wyjście | ||
""" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
""" | ||
zainstalowane pakiety na moim środowisku: | ||
peewee - ORM dla sqlite | ||
|
||
""" | ||
|
||
peewee==3.16.1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
""" | ||
scrypt testowy umożliwiający testowanie user_manager.py | ||
|
||
""" | ||
|
||
|
||
|
||
from user_manager import UserManager | ||
from messages import MAIN_MENU | ||
|
||
|
||
def main(): | ||
# Inicjalizacja managera użytkowników | ||
manager = UserManager() | ||
manager.setup_database() | ||
|
||
while True: | ||
print(MAIN_MENU) | ||
choice = input("Wybierz opcję: ") | ||
|
||
if choice == "1": | ||
username = input("Podaj nazwę użytkownika: ") | ||
print("Wymagania dotyczące hasła:") | ||
print(UserManager.get_password_requirements()) | ||
|
||
while True: | ||
password = input("Podaj hasło: ") | ||
is_valid, message = UserManager.validate_password(password) | ||
print(message) | ||
if is_valid: | ||
break | ||
print("\nSpróbuj ponownie.") | ||
|
||
while True: | ||
email = input("Podaj email: ") | ||
is_valid, message = UserManager.validate_email(email) | ||
print(message) | ||
if is_valid: | ||
break | ||
print("\nPopraw email.") | ||
|
||
if manager.add_user(username, password, email): | ||
print("Użytkownik dodany pomyślnie!") | ||
|
||
elif choice == "2": | ||
user_id = int(input("Podaj ID użytkownika do usunięcia: ")) | ||
if manager.delete_user(user_id): | ||
print("Użytkownik usunięty pomyślnie!") | ||
|
||
elif choice == "3": | ||
user_id = int(input("Podaj ID użytkownika do edycji: ")) | ||
email = input("Podaj nowy email (Enter aby pominąć): ") | ||
password = input("Podaj nowe hasło (Enter aby pominąć): ") | ||
|
||
email = email if email else None | ||
password = password if password else None | ||
|
||
if manager.edit_user(user_id, email, password): | ||
print("Dane użytkownika zaktualizowane pomyślnie!") | ||
|
||
elif choice == "4": | ||
users = manager.show_users() | ||
print("\nLista użytkowników:") | ||
print("ID | Username | Email") | ||
print("-" * 40) | ||
for user_id, username, email, created_at in users: | ||
print(f"{user_id:3} | {username:10} | {email} | {created_at}") | ||
|
||
elif choice == "5": | ||
username = input("Podaj nazwę użytkownika: ") | ||
password = input("Podaj hasło: ") | ||
user = manager.login(username, password) | ||
if user: | ||
print(f"Zalogowano pomyślnie jako {user.username}!") | ||
else: | ||
print("Błędne dane logowania!") | ||
|
||
elif choice == "0": | ||
print("Do widzenia!") | ||
break | ||
|
||
else: | ||
print("Nieprawidłowa opcja!") | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
""" | ||
moduł zawierający klasę UserManager, która zarządza użytkownikami w bazie danych | ||
'task: https://jira.is-academy.pl/browse/JPYDZR6MON-7' | ||
|
||
""" | ||
|
||
|
||
from peewee import * | ||
from datetime import datetime | ||
from hashlib import sha256 | ||
from typing import Optional, List, Tuple | ||
from messages import * | ||
import re | ||
|
||
|
||
# Inicjalizacja bazy danych | ||
db = SqliteDatabase('budget.db') | ||
|
||
class BaseModel(Model): | ||
class Meta: | ||
database = db | ||
|
||
class User(BaseModel): | ||
DoesNotExist = None | ||
username = CharField(max_length=55, unique=True) | ||
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. Zastanawiam się czy dla czytelności nie byłoby fajnie jawnie zadeklarować primary key i nazwać go np. user_id dla odróżnienia od innych id, które będą się pojawiały w bazie? Taka luźna propozycja :) |
||
password = CharField() | ||
email = CharField(unique=True) | ||
created_at = DateTimeField(default=datetime.now) | ||
|
||
class Meta: | ||
table_name = 'users' | ||
|
||
class UserManager: | ||
@staticmethod | ||
def hash_password(password: str) -> str: | ||
"""Haszuje hasło używając SHA-256.""" | ||
return sha256(password.encode()).hexdigest() | ||
|
||
def setup_database(self): | ||
"""Tworzy tabelę users jeśli nie istnieje.""" | ||
db.connect() | ||
db.create_tables([User], safe=True) | ||
db.close() | ||
|
||
def add_user(self, username: str, password: str, email: str) -> bool: | ||
""" | ||
Dodaje nowego użytkownika do bazy danych. | ||
Zwraca True jeśli się udało, False jeśli wystąpił błąd. | ||
""" | ||
try: | ||
User.create( | ||
username=username, | ||
password=self.hash_password(password), | ||
email=email | ||
) | ||
return True | ||
except IntegrityError: | ||
print("Błąd: Użytkownik lub email już istnieje!") | ||
return False | ||
except Exception as e: | ||
print(f"Wystąpił błąd: {e}") | ||
return False | ||
|
||
def delete_user(self, user_id: int) -> bool: | ||
""" | ||
Usuwa użytkownika o podanym ID. | ||
Zwraca True jeśli się udało, False jeśli wystąpił błąd. | ||
""" | ||
try: | ||
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. Czy pod blokiem try powinno być tyle działań? 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. Zależy :) Tutaj dwie linijki, więc raczej ok. Zależy czego się spodziewamy, bo |
||
query = User.delete().where(User.id == user_id) | ||
deleted_rows = query.execute() | ||
if deleted_rows > 0: | ||
return True | ||
print("Nie znaleziono użytkownika o podanym ID.") | ||
return False | ||
except Exception as e: | ||
print(f"Wystąpił błąd: {e}") | ||
return False | ||
|
||
def edit_user(self, user_id: int, email: Optional[str] = None, | ||
password: Optional[str] = None) -> bool: | ||
""" | ||
Edytuje dane użytkownika. | ||
Zwraca True jeśli się udało, False jeśli wystąpił błąd. | ||
""" | ||
try: | ||
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. Przy edycji użytkownika mogę już podać dowolne hasło i maila. Nie było weryfikacji. |
||
user = User.get_by_id(user_id) | ||
if email: | ||
user.email = email | ||
if password: | ||
user.password = self.hash_password(password) | ||
user.save() | ||
return True | ||
except User.DoesNotExist: | ||
print("Nie znaleziono użytkownika o podanym ID.") | ||
return False | ||
except IntegrityError: | ||
print("Błąd: Email już istnieje!") | ||
return False | ||
except Exception as e: | ||
print(f"Wystąpił błąd: {e}") | ||
return False | ||
|
||
def login(self, username: str, password: str) -> Optional[User]: | ||
""" | ||
Logowanie użytkownika. | ||
Zwraca obiekt User jeśli dane są poprawne, None w przeciwnym razie. | ||
""" | ||
try: | ||
user = User.get(User.username == username) | ||
if user.password == self.hash_password(password): | ||
return user | ||
return None | ||
except User.DoesNotExist: | ||
return None | ||
|
||
def show_users(self) -> List[tuple[int, str, str, datetime]]: | ||
""" | ||
Zwraca listę wszystkich użytkowników. | ||
""" | ||
users = [] | ||
for user in User.select(): | ||
users.append((user.id, user.username, user.email, user.created_at)) | ||
return users | ||
|
||
@staticmethod | ||
def get_password_requirements() -> str: | ||
return PASSWORD_REQUIREMENTS | ||
|
||
@staticmethod | ||
def validate_password(password: str) -> tuple[bool, str]: | ||
if len(password) < 8: | ||
return False, PASSWORD_TOO_SHORT | ||
|
||
if not any(c.isupper() for c in password): | ||
return False, PASSWORD_NO_UPPERCASE | ||
|
||
if not any(c.islower() for c in password): | ||
return False, PASSWORD_NO_LOWERCASE | ||
|
||
if not any(c.isdigit() for c in password): | ||
return False, PASSWORD_NO_DIGIT | ||
|
||
if not any(c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in password): | ||
return False, PASSWORD_NO_SPECIAL | ||
|
||
return True, PASSWORD_OK | ||
|
||
@staticmethod | ||
def validate_email(email: str) -> tuple[bool, str]: | ||
"""Walidacja emaila używając regex""" | ||
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' | ||
if re.match(pattern, email): | ||
return True, "Email jest poprawny" | ||
return False, "Nieprawidłowy format emaila" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
U mnie to przeszkadzało zainstalować pakiety z pliku requirements ;) musiałem dodać
#
do każdej linii