Skip to content
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

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion README.md
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

```
Binary file added budget.db
Binary file not shown.
50 changes: 50 additions & 0 deletions messages.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
"""
7 changes: 7 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""
zainstalowane pakiety na moim środowisku:
peewee - ORM dla sqlite

"""
Comment on lines +1 to +5
Copy link
Collaborator

@marekwocka marekwocka Nov 22, 2024

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


peewee==3.16.1
87 changes: 87 additions & 0 deletions test_user_manger.py
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()
155 changes: 155 additions & 0 deletions user_manager.py
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)
Copy link
Collaborator

Choose a reason for hiding this comment

The 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:
Copy link
Collaborator

@ilsalunte ilsalunte Nov 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Czy pod blokiem try powinno być tyle działań?

Copy link
Collaborator

Choose a reason for hiding this comment

The 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 Exception jest dość zagadkowe. Im lepiej opisane błędy tym lepiej.

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:
Copy link
Collaborator

Choose a reason for hiding this comment

The 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"