1

Тема: Чи буде таке рішення відповідати ООП, MVC та здоровому глузду?

Завдання створити записник із застосуванням ООП.

app.py

#!/python
"""
        Application for storing and managing contacts

"""

from views import ContactView


def main():
    while True:
        print('Commands: 1 - List; 2 - Search; 3- Add; 4 - Delete; 0 - Exit')
        command = input(' ?')
        if command == '1':
            ContactView.display_contact_list()
        elif command == '2':
            ContactView.search_contact()
        elif command == '3':
            ContactView.add_contact()
        elif command == '4':
            ContactView.del_contact()
        elif command in {'0', 'q'}:
            break


if __name__ == '__main__':
    print(__doc__)
    main()

settings.py

#!/python

import sqlite3

conn = sqlite3.connect('contact.db')

conn.execute("""create table if not exists CONTACT
    (ID    INT PRIMARY KEY NOT NULL,
     FIRST_NAME TEXT NOT NULL,
     LAST_NAME TEXT NOT NULL,
     PHONE CHAR(15));""")

conn.commit()

models.py

#!/python
from settings import conn


class Contact:
    """ Class Contact """

    def __init__(self, first_name, last_name, phone):
        self.first_name = first_name
        self.last_name = last_name
        self.phone = phone

    def __str__(self):
        return "Name:{0} {1}\nPhone:{2}".format(self.first_name,
                                                self.last_name,
                                                self.phone)

    @staticmethod
    def get_contact_list():
        cursor = conn.execute("""
            select * from CONTACT; 
            """)
        return cursor

    @staticmethod
    def delete_contact(pk):
        conn.execute("""  
            DELETE FROM CONTACT WHERE ID=?;
            """, (pk,))
        conn.commit()

    @staticmethod
    def search_contact(ss):  # ss is string_to_seacrh 
        cursor = conn.execute("""  
            SELECT * FROM CONTACT WHERE 
            FIRST_NAME LIKE ?  OR LAST_NAME LIKE ? OR PHONE LIKE ?; 
            """, ('%{}%'.format(ss), '%{}%'.format(ss), '%{}%'.format(ss),))
        return cursor

    def is_contact(self):
        cursor = conn.execute("""  
            SELECT * FROM CONTACT WHERE FIRST_NAME=? AND LAST_NAME=? LIMIT 1;
            """, (self.first_name, self.last_name))
        return len(cursor.fetchall()) == 1

    def save_contact(self):
        cursor = conn.execute("""  
            SELECT ID FROM CONTACT ORDER BY id DESC LIMIT 1;
            """)  # get last id from database
        max_id_row = [row for row in cursor]
        pk = max_id_row[0][0] + 1 if len(max_id_row) else 1
        conn.execute("""
            INSERT INTO CONTACT VALUES (?, ?, ?, ?);
            """, (pk, self.first_name, self.last_name, self.phone))
        conn.commit()

views.py

#!/python

from models import Contact


class ContactView:
    """ Views for Contacts """

    prefix = '\n\t=== > '

    def _input_data(value_name, value_length=1):
        """ Control for input data length """

        while True:
            print(ContactView.prefix, value_name,
                  ' (min length - {})'.format(value_length))
            value = input(' ?')
            if len(value) >= value_length:
                return value

    def _contact_list_view(contact_list=[]):
        """ Dispaly table """

        print('-' * 111)
        print(':{:5}: {:^40} : {:^40} : {:^15} :'.format(
            'N', 'First Name', 'Last Name', 'Phone'))
        print('-' * 111)
        for k, contact in enumerate(contact_list):
            print(':{:5}: {:^40} : {:^40} : {:^15} :'.format(
                k + 1, *contact[1:]))
        print('-' * 111)

    def display_contact_list():
        """ Dispaly all contacts """
        cursor = Contact.get_contact_list()
        contact_list = cursor.fetchall()
        ContactView._contact_list_view(contact_list)
        return contact_list

    def add_contact():
        """ Add contact if not exist """

        first_name = ContactView._input_data('Enter First Name', 3)
        last_name = ContactView._input_data('Enter Last Name', 3)
        phone = ContactView._input_data('Enter Phone ', 3)
        contact = Contact(first_name, last_name, phone)
        if contact.is_contact():
            print(ContactView.prefix,
                  'Can not add contact. This contact is present in database.')
            return
        contact.save_contact()
        print(contact, ContactView.prefix, 'contact added')

    def del_contact():
        """ Delete contact is exist """

        contact_list = ContactView.display_contact_list(
        )  # display contact list for delete choose to
        if not contact_list:
            print(ContactView.prefix,
                  'Database is empty.')
            return
        pk = input('Enter contact number to delete ?')
        pk = int(pk) - 1 if pk.isdigit() else -1  # make 0-based
        if 0 <= pk <= len(contact_list) - 1:  # check is pk in range
            Contact.delete_contact(contact_list[pk][0])
            print(ContactView.prefix, 'Deleted')
            return
        print(ContactView.prefix, 'Number incorrect.')

    def search_contact():
        """ Search contact by name or phone and display """

        ss = ContactView._input_data('Enter string to search', 2)
        cursor = Contact.search_contact(ss)
        ContactView._contact_list_view(cursor.fetchall())

2

Re: Чи буде таке рішення відповідати ООП, MVC та здоровому глузду?

50 переглядів і ні одного зауваження.
або все ідеально і нема до чого доколупатися, або все - повна дурня і не вартує навіть зауваження
:(

3

Re: Чи буде таке рішення відповідати ООП, MVC та здоровому глузду?

для save_contact не потрібно вказувати id в INSERT
https://stackoverflow.com/questions/934 … sqlite-row

for k, contact in enumerate(contact_list):

тут логічніше вивести id як на мене, тоді з видаленням простіше

Подякували: ping1

4

Re: Чи буде таке рішення відповідати ООП, MVC та здоровому глузду?

ping написав:

50 переглядів і ні одного зауваження.
або все ідеально і нема до чого доколупатися, або все - повна дурня і не вартує навіть зауваження
:(

Ну чому зразу не вартує навіть зауваження? Просто не можна підібрати правильних слів :)

5

Re: Чи буде таке рішення відповідати ООП, MVC та здоровому глузду?

Master_Sergius написав:
ping написав:

50 переглядів і ні одного зауваження.
або все ідеально і нема до чого доколупатися, або все - повна дурня і не вартує навіть зауваження
:(

Ну чому зразу не вартує навіть зауваження? Просто не можна підібрати правильних слів :)

ну, якщо речі називати своїми іменами - не думаю, що модератор буде проти.
зрештою - є спойлер )

6

Re: Чи буде таке рішення відповідати ООП, MVC та здоровому глузду?

Пишіть як є під спойлером - не забаню)

7

Re: Чи буде таке рішення відповідати ООП, MVC та здоровому глузду?

Voron написав:

для save_contact не потрібно вказувати id в INSERT
https://stackoverflow.com/questions/934 … sqlite-row

ну, це якщо поле створене як autoincrement.
а тут вручну визначаються  ID

for k, contact in enumerate(contact_list):

тут логічніше вивести id як на мене, тоді з видаленням простіше

логічніше, але для користувача умовного - нова сутність - якесь ID про яке він не знає нічого. тому реалізував через порядковий номер.

8

Re: Чи буде таке рішення відповідати ООП, MVC та здоровому глузду?

Для початку проясніть трошки хоча би за той файл "settings.py" - створення отак таблиці як на мене, то не є добре. Або воно автоматично має створити при виконанні міграцій (але це у вас не джанго ніяке), або мати у проекті спеціальний sql-файл, який легко натягується на базу у потрібному бд сервері. І налаштувань там в принципі ніяких нема (чому ж він зветься settings?), то є зайвий файл.

Далі, навіщо ускладнювати собі життя і розробляти простеньку програму із CLI у стилі MVC?

І нарешті, клас Contact - для чого він призначений? З одного боку, ініціалізується "новим контактом", з іншого боку - дозволяє проводити операції над усією базою. Як мінімум, це грубе порушення SRP (принцип єдиного обов'язку), а також виглядить жахливо.

Це те, що зразу кинулося у вічі. Детальніше не вдивлявся.

Подякували: 0xDADA11C7, ping2

9

Re: Чи буде таке рішення відповідати ООП, MVC та здоровому глузду?

Master_Sergius написав:

І нарешті, клас Contact - для чого він призначений? З одного боку, ініціалізується "новим контактом", з іншого боку - дозволяє проводити операції над усією базою. Як мінімум, це грубе порушення SRP (принцип єдиного обов'язку), а також виглядить жахливо.
.

Добре, якщо замість класу Contact  у мене буде клас AddressBook - він то матиме право працювати з усіма записами?

я щось заплутався.
у джанго ми створюємо клас User(models.Model) і через нього  можемо працювати з усіма користувачами
хіба це не аналогічно?

10

Re: Чи буде таке рішення відповідати ООП, MVC та здоровому глузду?

ping написав:

Добре, якщо замість класу Contact  у мене буде клас AddressBook - він то матиме право працювати з усіма записами?

я щось заплутався.
у джанго ми створюємо клас User(models.Model) і через нього  можемо працювати з усіма користувачами
хіба це не аналогічно?

Можна зробити класс AddressBook який буде проводити різні операціями над записами. При цьому, буде нормально мати окремо клас Contact, що відповідає моделі.

Так, у джанго система схожа до того, що у Вас. І, здається, покликано для того, щоб полегшити для програміста розробку, але теж порушують цей принцип єдиного обов'язку, imho. І Вам же зовсім не обов'язково наслідувати джангу, зробіть так, як вважаєте буде краще і простіше. Ніхто не казав, що django - ідеал, але, звісно, крутий фреймворк :)

Подякували: ping1

11

Re: Чи буде таке рішення відповідати ООП, MVC та здоровому глузду?

Можна зробити класс AddressBook який буде проводити різні операціями над записами. При цьому, буде нормально мати окремо клас Contact, що відповідає моделі.

добре. прошу глянути на такий варіант:
app.py

#!/usr/bin/env
"""
        Application for storing and managing contacts
"""

from views import AddressBookView

ab_view = AddressBookView()


def main():
    while True:
        print('Commands: 1 - List; 2 - Search; 3- Add; 4 - Delete; 0 - Exit')
        command = input(' ?')
        if command == '1':
            ab_view.display_contact_list()
        elif command == '2':
            ab_view.search_contact()
        elif command == '3':
            ab_view.create_new_contact()
        elif command == '4':
            ab_view.del_contact()
        elif command in {'0', 'q'}:
            break


if __name__ == '__main__':
    print(__doc__)
    main()

models.py

import sqlite3
from os import path

SQL_FILENAME = 'addresbook.sqlite'


class Contact:
    def __init__(self, first_name, last_name, phone):
        self.first_name = first_name
        self.last_name = last_name
        self.phone = phone

    def __str__(self):
        return "Name:{0} {1}\nPhone:{2}".format(self.first_name,
                                                self.last_name,
                                                self.phone)


class AddressBook:
    """ Class AddressBook """

    def __init__(self):
        if not path.exists(SQL_FILENAME):
            self.conn = sqlite3.connect(SQL_FILENAME)
            self.conn.execute("""CREATE TABLE IF NOT EXISTS CONTACT 
                            (ID    INTEGER PRIMARY KEY AUTOINCREMENT ,
                            FIRST_NAME CHAR(150),
                            LAST_NAME CHAR(150),
                            PHONE CHAR(15));
                            """)
            self.conn.commit()
        else:
            self.conn = sqlite3.connect(SQL_FILENAME)

    def __str__(self):
        return "Address Book. Total contacts : {}".format(len(self.get_contact_list()))

    def get_contact_list(self):
        cursor = self.conn.execute("""
            SELECT * FROM CONTACT; 
            """)
        contact_list = cursor.fetchall()
        return contact_list

    def delete_contact(self, contact):
        self.conn.execute("""  
            DELETE FROM CONTACT WHERE FIRST_NAME = ?  AND LAST_NAME = ? AND PHONE = ?; 
            """, (contact.first_name, contact.last_name, contact.phone))
        self.conn.commit()

    def search_contact(self, ss):  # ss is string_to_seacrh
        cursor = self.conn.execute("""  
            SELECT * FROM CONTACT WHERE 
            FIRST_NAME LIKE ?  OR LAST_NAME LIKE ? OR PHONE LIKE ?; 
            """, ('%{}%'.format(ss), '%{}%'.format(ss), '%{}%'.format(ss),))
        return cursor.fetchall()

    def is_contact(self, contact):
        cursor = self.conn.execute("""  
            SELECT * FROM CONTACT WHERE FIRST_NAME=? AND LAST_NAME=? LIMIT 1;
            """, (contact.first_name, contact.last_name))
        return len(cursor.fetchall()) == 1

    def save_contact(self, contact):
        self.conn.execute("""
            INSERT INTO CONTACT ('first_name', 'last_name', 'phone') VALUES (?, ?, ?);
            """, (contact.first_name, contact.last_name, contact.phone))
        self.conn.commit()

views.py

from models import Contact, AddressBook

ab = AddressBook()


class BaseView:
    def __init__(self):
        self.prefix = '\n\t=== > '
        self.value_length = 1

    def _input_data(self, value_name, value_length):
        """ Control for input data length """

        while True:
            print(self.prefix, value_name,
                  ' (min length - {})'.format(value_length))
            value = input(' ?')
            if len(value) >= value_length:
                return value


class ContactView(BaseView):
    def add_contact(self):
        first_name = self._input_data('Enter First Name', 3)
        last_name = self._input_data('Enter Last Name', 3)
        phone = self._input_data('Enter Phone ', 3)
        contact = Contact(first_name, last_name, phone)
        return contact


class AddressBookView(BaseView):
    """ Views for Contacts """

    @staticmethod
    def _contact_list_view(contact_list):
        """ Display table """

        print('-' * 111)
        print(':{:5}: {:^40} : {:^40} : {:^15} :'.format(
            'N', 'First Name', 'Last Name', 'Phone'))
        print('-' * 111)
        for k, contact in enumerate(contact_list):
            print(':{:5}: {:^40} : {:^40} : {:^15} :'.format(
                k + 1, *contact[1:]))
        print('-' * 111)

    def display_contact_list(self):
        """ Display all contacts """
        contact_list = ab.get_contact_list()
        self._contact_list_view(contact_list)
        return contact_list

    def create_new_contact(self):
        """ Add contact if not exist """
        contact_view = ContactView()
        contact = contact_view.add_contact()
        if ab.is_contact(contact):
            print(self.prefix,
                  'Can not add contact. This contact is present in database.')
            return
        ab.save_contact(contact)

    def del_contact(self):
        """ Delete contact if exist """

        contact_list = self.display_contact_list()  # display contact list for delete choose to
        if not contact_list:
            print(self.prefix,
                  'Database is empty.')
            return
        pk = input('Enter contact number to delete ?')
        pk = int(pk) - 1 if pk.isdigit() else -1  # make 0-based
        if 0 <= pk <= len(contact_list) - 1:  # check is pk in range
            contact = Contact(*contact_list[pk][1:])
            ab.delete_contact(contact)
            print(self.prefix, 'Deleted')
        else:
            print(self.prefix, 'Number incorrect.')

    def search_contact(self):
        """ Search contact by name or phone and display """

        ss = self._input_data('Enter string to search', 2)
        contact_list = ab.search_contact(ss)
        self._contact_list_view(contact_list)

12

Re: Чи буде таке рішення відповідати ООП, MVC та здоровому глузду?

Особисто мені стало краще :)
Може ще є люди, які висловлять свою точку зору. Той же код рівью зазвичай робиться 2-4 людьми, але ніяк не 1 :)

Подякували: ping1

13

Re: Чи буде таке рішення відповідати ООП, MVC та здоровому глузду?

Master_Sergius написав:

Особисто мені стало краще :)
Може ще є люди, які висловлять свою точку зору. Той же код рівью зазвичай робиться 2-4 людьми, але ніяк не 1 :)

дякую за зауваження.

а ще мене гнітить - чи то добре  прямо в класі при ініціалізації встановлювати з'єднання з базою даних?

14

Re: Чи буде таке рішення відповідати ООП, MVC та здоровому глузду?

Ну, створення таблиці варто все ж винести в окремий sql-файл. А сам конекшн - ну, якщо цей об'єкт буде сінглтоном, то нормально. Усе залежить від того, що буде з вашим проектом далі. Яким чином розширятимете, як використовуватимете і т.д. і т.п.
І ще кілька тверджень для підняття самооцінки чи що:
1. Найкраще - ворог хорошого.
2. Усі пишуть гівнокод. Навіть якщо зараз код здається ідеальним, через кілька років він може вважатися гівнокодом. А може й ні, якщо код таки ідеальний :)
3. Пишіть юніттести. Хороший код повинен легко тестуватися.

Подякували: ping1

15

Re: Чи буде таке рішення відповідати ООП, MVC та здоровому глузду?

спочатку  визначiться   - програма  повинна  вiдповiдати здоровому  глузду (та  виконувати необхiдну функцiональнiсть)  чи вiдповiдати ООП з MVC.

16 Востаннє редагувалося ping (17.07.2017 16:56:57)

Re: Чи буде таке рішення відповідати ООП, MVC та здоровому глузду?

caballero написав:

спочатку  визначiться   - програма  повинна  вiдповiдати здоровому  глузду (та  виконувати необхiдну функцiональнiсть)  чи вiдповiдати ООП з MVC.

вважаю - для тестових завдань здоровий глузд полягає лише у тому, щоб не створювати надмірні сутності, створивши тільки ті, що вимагаються в постановці завдання.
а ООП з MVC в даному завданні - вимагаються.

зрештою - прокоментуйте по пунктах в порядку їх поступлень :) :
1. ООП
2. MVC
3. здоровий глузд