Python (Flask) — система регистрации пользователей

Я работаю над простым приложением 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 ответ
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 операций».

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *