Я работаю над простым приложением CRUD как личным проектом с использованием Flask. Я сейчас работаю над служба пользователя и я только что завершил процесс регистрации. Я также стараюсь использовать как можно меньше библиотек, поэтому вы не увидите ORM или Flask-wtf (в учебных целях).
models.py: содержит 1 класс —
User
. ВUser
класс — это просто представлениеUser
объект, в этом классе нет метода, кроме__str__
метод.services.py: содержит 1 класс —
UserService
. Никаких конструкторов или переменных класса. ВUserService
используется для взаимодействия сUser
например, создать нового пользователя, обновить параметр пользователя или удалить пользователя. Большинство изUserService
методы требуетUser
в качестве параметра ожидайте, что метод создаст новыйUser
,add_user()
repo.py: содержит 1 класс —
UserRepository
. Поскольку я не использую ORM, мне нужен был способ взаимодействия с базой данных для добавления, удаления или обновления строк вUser
стол. Я мог бы напрямую задавать вопросыUser
сервис, но я думаю, что на самом деле нарушу разделение забот;Route.py: Как только пользователь заполнит
register.html
регистрационная форма, форма отправляется на/new-user
маршрут.
Меня интересует несколько вещей:
Я читал, что фиксация в репозитории с использованием sqlite требует приличного количества ресурсов. У меня есть метод сохранения в моем классе UserRepository, который просто выполняет conn.commit (). Но должен ли я вызывать этот метод каждый раз, когда я создаю нового пользователя или как он должен вызываться после создания x нового
Users
или x новых db-операций?Я конвертирую свой
active
переменная отBoolean
кInteger
(1 или 0), потому что именно так он установлен в моемUser
таблица в базе данных mySQL какtinyint(1) DEFAULT '1'
. Должен ли я также использовать 1/0 в моем коде или оставить его как True / False и преобразовать изBoolean
кInteger
в моемUserRepository
учебный класс?Если у вас есть другие неприятные моменты или проблемы с дизайном, которых мне не следует делать, дайте мне знать 🙂
Код ниже:
пользователь / routes.py
user = Blueprint('user', __name__, template_folder="templates")
@user.route('/register')
def login():
return render_template('register.html')
# register new user
@user.route('/new-user',methods = ['POST'])
def register_user():
# gather form data
form_email = request.form.get('email')
form_password = request.form.get('psw')
# register_user() will return a User object
new_user = UserService().register_user(form_email, form_password)
user_repository = UserRepository(conn, 'users')
user_repository.add_user(new_user)
user_repository.save()
# will probably change the return and add try-catch?
return "ok"
models.py (класс пользователя)
class User():
def __init__(self, email, password, registration_date,
active, sign_in_count, current_sign_in_on, last_sign_in_on):
self.email = email
self.password = password
self.registration_date = registration_date
self.active = active
# Activity tracking
self.sign_in_count = sign_in_count
self.current_sign_in_on = current_sign_in_on
self.last_sign_in_on = last_sign_in_on
def __str__(self):
user_attributes = vars(self)
return (', '.join("%s: %s" % item for item in user_attributes.items()))
services.py (класс UserService)
class UserService():
def register_user(self,
email,
password):
# sign-in count is 1 since it is a new user
sign_in_count = 1
# today dates for registration date, current sign-in date and last sign-in date since it's a new user
today_date = datetime.today().strftime('%Y-%m-%d')
active = True
new_user = User(email, password, today_date, active,
sign_in_count, today_date, today_date)
return new_user
def desactivate_user(self, User):
if User.active == False:
print(f"User {User.email} is already inactive")
User.active = False
def reactive_user(self, User):
if User.active == True:
print(f"User {User.email} is already active")
User.active = True
def is_active(self, User):
return User.is_active
def update_activity_tracking(self, User, ip_address):
User.sign_in_count += 1
User.last_sign_in_on = User.current_sign_in_on
User.current_sign_in_on = datetime.datetime.now()
def update_password(self, User, new_password):
User.password = get_hashed_password(new_password)
repo.py (класс UserRepository)
class UserRepository():
def __init__(self, conn, table):
self.conn = conn
self.table = table
def add_user(self, User):
sql = "INSERT INTO users (email, password, is_active, sign_in_count, current_sign_in_on, last_sign_in_on) VALUES (%s, %s, %s, %s, %s, %s)"
cursor = self.conn.cursor()
# the is_active column in the DB is a tinyint(1). True = 1 and False = 0
if User.active == True:
is_active = 1
is_active = 0
cursor.execute(sql, ( User.email, User.password, is_active, User.sign_in_count, User.current_sign_in_on, User.last_sign_in_on))
resp = cursor.fetchall()
return resp
def delete_user(self):
return ""
def get_user(self):
return ""
def save(self):
self.conn.commit()
1 ответ
Мнения по таким вопросам всегда расходятся, но ваши UserService
class кажется серьезным случаем чрезмерной инженерии.
Во-первых, три метода не предлагают ничего существенного:
desactivate_user()
и reactive_user()
просто установите логические атрибуты на
User
пример; и is_active()
просто возвращает атрибут. Я бы посоветовал вам упростить ситуацию: просто работайте с экземпляром пользователя напрямую.
Во-вторых, update_activity_tracking()
делает больше для экземпляра пользователя, но нет очевидной причины не перемещать метод в User
класс тоже.
В-третьих update_password()
— хороший пример ситуации, когда вы можете использовать свойство, потому что вам нужно выполнить некоторые вычисления перед сохранением атрибута. Вот основной шаблон:
class User:
def __init__(self, password):
# Initialize an under-the-hood attribute.
self._password = None
# Call the setter.
self.password = password
@property
def password(self):
# The getter just returns it.
return self._password
@property.setter
def password(self, new_password):
# The setter does the needed calculation.
self._password = get_hashed_password(new_password)
В-четвертых, register_user()
метод естественным образом подходит для classmethod
в User
учебный класс:
class User:
@classmethod
def register_user(cls, email, password):
# Python allows you to call functions in keyword style even if the
# function defines arguments as required positionals. You can use
# this feature to simplify the code (eliminate some temporary
# variables in this case) while still preserving code readability.
today = datetime.today().strftime('%Y-%m-%d')
return cls(
email,
password,
registration_date = today,
active = 1,
sign_in_count = 2,
current_sign_in_on = today,
last_sign_in_on = today,
)
В User.__init__()
метод имеет большое количество обязательных параметров. Возможно, этого требует ваш вариант использования, но вы могли бы подумать, какой из атрибутов действительно требуется во всех случаях использования. Например, метод по умолчанию может использовать registration_date
если две даты входа в систему не указаны (как мы сделали выше в register_user()
). Независимо от этого решения, если тестирование и отладка напрямую взаимодействуют с __init__()
утомительно, вы можете добавить в класс больше удобства, снова используя @classmethod
механизм.
Управление паролями пользователей — дело непростое, по крайней мере, в реальном мире. Хорошо, что вы храните хешированный пароль, а не простой текст, но я бы не стал включать даже атрибут хешированного пароля ни в какую печать, ведение журнала и т. Д. Настройте свой User.__str__()
метод соответственно.
В UserRepository.add_user()
вы можете упростить. Если вы проверяете истину, просто делайте это напрямую (if user.active
), а не косвенно (if user.active == True
). Также, bool
значения в Python являются подклассом int
(Например, 199 + True == 200
), поэтому вы можете полностью удалить условную логику и просто сделать что-то вроде этого:
def add_user(self, user):
# Long lines can be made more readable by formatting
# them in the style of pretty-printed JSON. Notice how
# easy this style is to read, scan visually, and edit flexibly.
sql=""'
INSERT INTO users (
email,
password,
is_active,
sign_in_count,
current_sign_in_on,
last_sign_in_on
)
VALUES (%s, %s, %s, %s, %s, %s)
'''.strip()
cursor = self.conn.cursor()
cursor.execute(
sql,
(
user.email,
user.password,
int(user.active),
user.sign_in_count,
user.current_sign_in_on,
user.last_sign_in_on,
)
)
return cursor.fetchall()
Ваша заглавная буква User
переменная в различных методах несовместима как с вашим собственным использованием, так и с нормами в более широком сообществе Python: FooBar
для имен классов и foo_bar
например, переменные — это наиболее распространенный подход.
Операции с БД могут завершиться ошибкой. Если это всего лишь начальное упражнение, вы можете спокойно игнорировать проблему, но если ваша цель — сделать приложение более надежным, операции должны быть заключены в try-except
структур, как указано в комментариях к коду.
По моему опыту, подобные веб-приложения будут иметь различные классы, представляющие интересующие объекты (Пользователь, Книга, Автор и т. Д.). И некоторым из этих классов потребуются операции БД: создание, чтение, обновление, удаление и т. Д. И эти операции БД будут довольно общими в том смысле, что если вы напишете хороший метод для обновления одного типа экземпляра (например, пользователя) , его часто можно заставить делать то же самое для других типов экземпляров, при условии, что основные классы (User, Book и т. д.) имеют атрибуты, свойства или методы для доставки информации (такие вещи, как имя таблицы и список (COL_NAME, ATTRIBUTE_VALUE)
кортежи), которые необходимы универсальному методу БД для выполнения своей операции. Все это говорит о том, что UserRepository
может быть слишком узким фокусом. Вместо этого подумайте, можно ли и как преобразовать этот класс в простой Repository
.
Если это веб-приложение предназначено для длительного использования, вы захотите подключить его для использования logging
framework в какой-то момент (а не просто для печати), но это может произойти, когда вы будете готовы узнать об этом.
Что касается вашего вопроса о частоте сохранения изменений в БД, не беспокойтесь об этом и не задумывайтесь над этим. Создайте свое приложение с разумным кодом Python — у вас хороший старт — и если вашему приложению когда-либо понадобится достичь более высокого масштаба, решайте проблему, когда она четко видна на горизонте. Часто бывает проще изменить БД, масштабировать в сторону (больше запущенных экземпляров вашего приложения в разных процессах или на разных серверах) или изменить схему данных, чем написать сложный код «сохранить каждые N операций».