code: https://github.com/labradorescence/p3-oo-many-to-many-lab
In this lab we will implement a many-to-many relationship between a `Author
`, `Book
`, and `Contract
`.
$pipenv install # to create your virtual environment
$pipenv shell #to enter the virtual environment
#pytest -x #to run your tests
-=
Book class
Create a Book class that has the following attributes: `title` (string)
class Book:
def __init__(self, title):
self.title = title
#pass #a temporary placeholder
-=
- =
Author class
Create an Author class that has the following attributes: `name` (string)
class Author:
def __init__(self, name):
self.name = name
pass
=-
=-
Contract class: Intermediary class
Create a Contract class that has the following properties:
`author` (Author object), `book` (Book object), `date` (string), and `royalties` (int).
-The author property should be an instance of the Author class,
-while the book property should be an instance of the Book class.
-The date property should be a string that represents the date when the contract was signed,
-while the royalties property should be a number that represents the percentage of royalties that the author will receive for the book.
- All setters should `raise Exception` upon failure.
self._author
underscored attributes are intended to be treated as internal or protected. It’s a common practice to use underscores to indicate that an attribute is not meant to be accessed directly from outside the class.
class Contract:
def __init__(self, author, book, date, royalties):
self.author = author
self.book = book
self.date = date
self.royalties = royalties
#'_'as internal or protected
#not meant to be accessed directly from outside the class.
@property
def author(self):
return self._author
@author.setter
def author(self, value):
if isinstance(value, Author):
self._author = value
else:
raise Exception
@property
def book(self):
return self._book
@book.setter
def book(self, value):
if isinstance(value, Book):
self._book = value
else:
raise Exception
@property
def date(self):
return self._date
@date.setter
def date(self, value):
if isinstance(value, str):
self._date = value
else:
raise Exception
@property
def royalties(self):
return self._royalties
@royalties.setter
def royalties(self, value):
if isinstance(value, int):
self._royalties = value
else:
raise Exception
— -=
All classes should also keep track of `all` members using a class variable.
# import ipdb
class Author:
all = [] #<------ add this
def __init__(self, name):
self.name = name
Author.all.append(self)#<------ add this
class Book:
all = []#<------ add this
def __init__(self, title):
self.title = title
Book.all.append(self)#<------ add this
class Contract:
all = [] #<------ add this
def __init__(self, author, book, date, royalties):
self._author = author
self._book = book
self._date = date
self._royalties = royalties
Contract.all.append(self)#<------ add this
@property
.
.
.
.
.
— -=
check
#many_to_many.py
import ipdb
b1 = Book("A Dog's Purpose")
a1 = Author("W. Bruce Cameron")
c1 = Contract(a1, b1, "Monday", 30)
b2 = Book("The Three Body Problem")
a2 = Author("Liu Cixin")
c2 = Contract(a2, b2, "Tuesday", 20)
ipdb.set_trace()
$python3 lib/many_to_many.py
#add a new book
ipdb> Book("Kindred")
#get all books' titles
ipdb> for each_b in Book.all:
print(each_b.title)
#add a new author
ipdb> Author("Octavia E Butler")
#get all authors name
ipdb> for each_a in Author.all:
print(each_a.name)
#get all contracts
ipdb> for each_c in Contract.all:
print(each_c.book.title)
A Dog's Purpose
The Three Body Problem
#notice new book is not in the contract
— -=
The `Author` class, instance methods
The `Author` class should have the following methods:
=-
a1.contracts()
- `contracts(self)`: This method should return a list of related contracts.
ipdb> for contract in Contract.all:
print(contract.author.name)
W. Bruce Cameron
— — =
so
#Author class
class Author:
def __init__(self, name):
.....
def contracts(self):
return [contract for contract in Contract.all #list comprehension
if contract.author == self]
=-
check
#invoke the instance method
ipdb> a1.contracts()
[<__main__.Contract object at 0x102706070>]
#print it with more descirption
ipdb> for each_c in a1.contracts():
print(a1.name, "::::::", each_c.author.name, each_c.book.title,
each_c.date, each_c.royalties)
W. Bruce Cameron :::::: W. Bruce Cameron A Dog's Purpose Monday 30
=-
a1.books()
=-
`books(self)`: This method should return a list of related books using the
`Contract` class as an intermediary.
ipdb> for contract in a1.contracts():
print(contract.book.title )
so
#Author class
class Author:
.
.
.
def books(self): #list comprehension
return [ contract.book for contract in self.contracts() ]
check
#return a list of author's related books
ipdb> a1.books()
[<__main__.Book object at 0x1004a4160>]
#get the title
ipdb> for each_b in a1.books():
print(each_b.title)
A Dog's Purpose
=-
a1.sign_contract(b1, “Jan 16th, 2024”, 50)
a1.sign_contract(Book(“A Cat’s Purpose”), “Jan 16th, 2024”, 50)
=-
`sign_contract(book, date, royalties)`: This method should create and return a new `Contract` object between the author and the specified book with the specified date and royalties
def sign_contract(self, book, date, royalties):
return Contract(self, book, date, royalties)
=-
check
#sign a new contract for the author 1
ipdb> a1.sign_contract(b1, "Jan 16th, 2024", 50)
<__main__.Contract object at 0x1101b4070>
#check all the contracts for author 1
ipdb> a1.contracts()
[<__main__.Contract object at 0x1051d29a0>, <__main__.Contract object at 0x1101b4070>]
#sign a new contract for the author 1 w a new book
a1.sign_contract(Book("A Cat's Purpose"), "Jan 16th, 2024", 50)
<__main__.Contract object at 0x11018c1c0>
#check all the contracts for author 1
ipdb> a1.contracts()
[<__main__.Contract object at 0x1051d29a0>, <__main__.Contract object at 0x1101b4070>, <__main__.Contract object at 0x11018c1c0>]
ipdb>
=-
a1.total_royalties()
=-
`total_royalties()`: This method should return the total amount of royalties
that the author has earned from all of their contracts.
#get royalties from the each contract
ipdb> for each_c in a1.contracts():
print(each_c.royalties)
30
50
50
#retreive them in a list
ipdb> [ each_c.royalties for each_c in a1.contracts()]
[30, 50, 50]
# get sum
ipdb> sum([ each_c.royalties for each_c in a1.contracts()])
130
so
class Author:
.
.
def total_royalties(self):
return sum([ each_c.royalties for each_c in self.contracts()])
check
#invoke the total_royalties method
ipdb> a1.total_royalties()
30
#add more contracts for this author to show sum example
ipdb> a1.sign_contract(Book("Catcher in the Rye"), "1951", 200)
<__main__.Contract object at 0x107623eb0>
#invoke the total_royalties method
ipdb> a1.total_royalties()
230
=-
`Contract` class, class method
=-
The `Contract` class should have the following methods:
- A class method `contracts_by_date`(cls, date): This method should return all contracts that have the same date as the date passed into the method.
In object-oriented programming, we often organize related functionalities into classes. We have aContract
class. Within this class, there is a class method defined using the @classmethod
decorator, named contracts_by_date
.
Here’s a breakdown:
- Class Method:
- class method belongs to the class itself
- The
@classmethod
decorator indicates that this method belongs to the class itself rather than an instance of the class. It's like a method that acts on the class level rather than on an individual contract.
2. Method Signature:
- The method is named
contracts_by_date
, and it takes two parameters:cls
(conventionally representing the class itself) anddate
. This method is designed to retrieve contracts based on a specific date.
process to get the answer
# get each contract in the all contracts
ipdb> [contract for contract in Contract.all ]
[<__main__.Contract object at 0x10464ac10>, <__main__.Contract object at 0x104657190>, <__main__.Contract object at 0x107623eb0>]
# get each contract's date in the all contracts
ipdb> [contract.date for contract in Contract.all ]
['Monday', 'Tuesday', '1951']
# get contract that matches the date, Tuesday
ipdb> [contract for contract in Contract.all if contract.date == "
Tuesday"]
[<__main__.Contract object at 0x104657190>]
#get book title, and date for the contract that matches the date tuesdasy
ipdb> [(contract.book.title, contract.date) for contract in Contract.all
if contract.date == "Tuesday"]
[('The Three Body Problem', 'Tuesday')]
so
@classmethod
def contracts_by_date(cls, date):
return [contract for contract in Contract.all
if contract.date == date]
check
$python3 lib/many_to_many.py
#returns a list of contracts that meet the criteria
ipdb> c1.contracts_by_date("Tuesday")
[<__main__.Contract object at 0x105232460>]
#returns details of contracts that meet the criteria
ipdb> for each_c in c1.contracts_by_date("Tuesday"):
print(each_c.book.title, each_c.author.name, each_c.date,
each_c.royalties)
The Three Body Problem Liu Cixin Tuesday 20
BIG CHECK
$pytest
b1.contracts()
FAILED module Test Book class has method contracts() that returns a list of its contracts — AttributeError: ‘Book’ object has no attribute ‘contracts’
class Book:
.....
def contracts(self):
return [ contract for contract in Contract.all
if contract.book == self ]
b1.authors()
FAILED module Test Book class has method authors() that returns a list of its authors — AttributeError: ‘Book’ object has no attribute ‘authors’
process
so
#book class
def authors(self):
return [ contract.author for contract in self.contracts()]
check
$pytest
14 passed!!
all
# import ipdb
class Book:
all = []
def __init__(self, title):
self.title = title
Book.all.append(self)
def contracts(self):
return [ contract for contract in Contract.all if contract.book == self ]
def authors(self):
return [ contract.author for contract in self.contracts()]
class Author:
all = []
def __init__(self, name):
self.name = name
Author.all.append(self)
def contracts(self):
return [ contract for contract in Contract.all if contract.author == self]
def books(self):
return [contract.book for contract in self.contracts()]
def sign_contract(self, book, date, royalties):
return Contract(self, book, date, royalties)
def total_royalties(self):
return sum([ each_c.royalties for each_c in self.contracts()])
class Contract:
all = []
def __init__(self, author, book, date, royalties):
self.author = author
self.book = book
self.date = date
self.royalties = royalties
Contract.all.append(self)
@property
def author(self):
return self._author
@author.setter
def author(self, author):
if isinstance(author, Author):
self._author = author
else:
raise Exception
@property
def book(self):
return self._book
@book.setter
def book(self, book):
if isinstance(book, Book):
self._book = book
else:
raise Exception
@property
def date(self):
return self._date
@date.setter
def date(self, date):
if isinstance(date, str):
self._date = date
else:
raise Exception
@property
def royalties(self):
return self._royalties
@royalties.setter
def royalties(self, royalties):
if isinstance(royalties, int):
self._royalties = royalties
else:
raise Exception
@classmethod
def contracts_by_date(cls, date):
return [contract for contract in Contract.all if contract.date == date]
b1 = Book("A Dog's Purpose")
a1 = Author("W. Bruce Cameron")
c1 = Contract(a1, b1, "Monday", 30)
b2 = Book("The Three Body Problem")
a2 = Author("Liu Cixin")
c2 = Contract(a2, b2, "Tuesday", 20)
# ipdb.set_trace()