diff --git a/README.md b/README.md index b598b7784..d3f44701c 100644 --- a/README.md +++ b/README.md @@ -1,182 +1,34 @@ -# Phase 3 Mock Code Challenge: Freebie Tracker +# Freebie Tracker -## Learning Goals +**Freebie Tracker** is your developer-sidekick for cataloging swag, giveaways, and promotional treasures snagged from hackathons, conferences, and events. Never lose track of that elusive T-shirt or sticker pack again. -- Write SQLAlchemy migrations. -- Connect between tables using SQLAlchemy relationships. -- Use SQLAlchemy to run CRUD statements in the database. +## Project Overview -*** +This app elegantly models the real-world relationships between: -## Key Vocab +- **Companies** dishing out freebies, +- **Developers** eagerly collecting them, +- **Freebies** themselves—linked to both their origin and their proud owners. -- **Schema**: the blueprint of a database. Describes how data relates to other - data in tables, columns, and relationships between them. -- **Persist**: save a schema in a database. -- **Engine**: a Python object that translates SQL to Python and vice-versa. -- **Session**: a Python object that uses an engine to allow us to - programmatically interact with a database. -- **Transaction**: a strategy for executing database statements such that - the group succeeds or fails as a unit. -- **Migration**: the process of moving data from one or more databases to one - or more target databases. - -*** +The goal? To provide a clear, intuitive system for logging, organizing, and querying your collection of coveted swag. -## Introduction +## Key Features -For this assignment, we'll be working with a freebie domain. +- **Transaction Logging:** Effortlessly record which developer scored what item from which company. +- **Company Freebie Catalog:** Instantly view every freebie distributed by any company. +- **Developer Inventory:** Quickly browse all swag owned by a developer. +- **Relationship Mapping:** Reveal who holds freebies from which companies — perfect for competitive bragging rights. +- **Seamless Transfers:** Easily transfer freebies between developers without breaking a sweat. -As developers, when you attend hackathons, you'll realize they hand out a lot of -free items (informally called _freebies_, or swag)! Let's make an app for -developers that keeps track of all the freebies they obtain. +## Installation -We have three models: `Company`, `Dev`, and `Freebie` +Clone the repo and dive right in: -For our purposes, a `Company` has many `Freebie`s, a `Dev` has many `Freebie`s, -and a `Freebie` belongs to a `Dev` and to a `Company`. +```bash +git clone https://github.com/yourusername/python-p3-freebie-tracker.git +cd python-p3-freebie-tracker +``` -`Company` - `Dev` is a many to many relationship. +You can explore the domain design and data model here: +[Freebie Tracker Domain Design] (https://dbdiagram.io/d/68336bb3b9f7446da31342a0) -**Note**: You should draw your domain on paper or on a whiteboard _before you -start coding_. Remember to identify a single source of truth for your data. - -## Instructions - -To get started, run `pipenv install && pipenv shell` while inside of this -directory. - -Build out all of the methods listed in the deliverables. The methods are listed -in a suggested order, but you can feel free to tackle the ones you think are -easiest. Be careful: some of the later methods rely on earlier ones. - -**Remember!** This mock code challenge does not have tests. You cannot run -`pytest` and you cannot run `learn test`. You'll need to create your own sample -instances so that you can try out your code on your own. Make sure your -relationships and methods work in the console before submitting. - -We've provided you with a tool that you can use to test your code. To use it, -run `python debug.py` from the command line. This will start an `ipdb` session -with your classes defined. You can test out the methods that you write here. You -are also encouraged to use the `seed.py` file to create sample data to test your -models and associations. - -Writing error-free code is more important than completing all of the -deliverables listed- prioritize writing methods that work over writing more -methods that don't work. You should test your code in the console as you write. - -Similarly, messy code that works is better than clean code that doesn't. First, -prioritize getting things working. Then, if there is time at the end, refactor -your code to adhere to best practices. - -**Before you submit!** Save and run your code to verify that it works as you -expect. If you have any methods that are not working yet, feel free to leave -comments describing your progress. - -*** - -## What You Already Have - -The starter code has migrations and models for the initial `Company` and `Dev` -models, and seed data for some `Company`s and `Dev`s. The schema currently looks -like this: - -### companies Table - -| Column | Type | -| ------------- | ------- | -| name | String | -| founding_year | Integer | - -### devs Table - -| Column | Type | -| ------ | ------ | -| name | String | - -You will need to create the migration for the `freebies` table using the -attributes specified in the deliverables below. - -*** - -## Deliverables - -Write the following methods in the classes in the files provided. Feel free to -build out any helper methods if needed. - -Remember: SQLAlchemy gives your classes access to a lot of methods already! -Keep in mind what methods SQLAlchemy gives you access to on each of your -classes when you're approaching the deliverables below. - -### Migrations - -Before working on the rest of the deliverables, you will need to create a -migration for the `freebies` table. - -- A `Freebie` belongs to a `Dev`, and a `Freebie` also belongs to a `Company`. - In your migration, create any columns your `freebies` table will need to - establish these relationships using the right foreign keys. -- The `freebies` table should also have: - - An `item_name` column that stores a string. - - A `value` column that stores an integer. - -After creating the `freebies` table using a migration, use the `seed.py` file to -create instances of your `Freebie` class so you can test your code. - -**After you've set up your `freebies` table**, work on building out the following -deliverables. - -### Relationship Attributes and Methods - -Use SQLAlchemy's `ForeignKey`, `relationship()`, and `backref()` objects to -build relationships between your three models. - -**Note**: The plural of "freebie" is "freebies" and the singular of "freebies" -is "freebie". - -#### Freebie - -- `Freebie.dev` returns the `Dev` instance for this Freebie. -- `Freebie.company` returns the `Company` instance for this Freebie. - -#### Company - -- `Company.freebies` returns a collection of all the freebies for the Company. -- `Company.devs` returns a collection of all the devs who collected freebies - from the company. - -#### Dev - -- `Dev.freebies` returns a collection of all the freebies that the Dev has collected. -- `Dev.companies`returns a collection of all the companies that the Dev has collected - freebies from. - -Use `python debug.py` and check that these methods work before proceeding. For -example, you should be able to retrieve a dev from the database by its -attributes and view their companies with `dev.companies` (based on your seed -data). - -### Aggregate Methods - -#### Freebie - -- `Freebie.print_details()`should return a string formatted as follows: - `{dev name} owns a {freebie item_name} from {company name}`. - -#### Company - -- `Company.give_freebie(dev, item_name, value)` takes a `dev` (an instance of - the `Dev` class), an `item_name` (string), and a `value` as arguments, and - creates a new `Freebie` instance associated with this company and the given - dev. -- Class method `Company.oldest_company()`returns the `Company` instance with - the earliest founding year. - -#### Dev - -- `Dev.received_one(item_name)` accepts an `item_name` (string) and returns - `True` if any of the freebies associated with the dev has that `item_name`, - otherwise returns `False`. -- `Dev.give_away(dev, freebie)` accepts a `Dev` instance and a `Freebie` - instance, changes the freebie's dev to be the given dev; your code should only - make the change if the freebie belongs to the dev who's giving it away diff --git a/freebies.db b/freebies.db new file mode 100644 index 000000000..1bfd3ce39 Binary files /dev/null and b/freebies.db differ diff --git a/lib/__pycache__/debug.cpython-312.pyc b/lib/__pycache__/debug.cpython-312.pyc new file mode 100644 index 000000000..ba102ab7f Binary files /dev/null and b/lib/__pycache__/debug.cpython-312.pyc differ diff --git a/lib/__pycache__/models.cpython-312.pyc b/lib/__pycache__/models.cpython-312.pyc new file mode 100644 index 000000000..9ea69fef9 Binary files /dev/null and b/lib/__pycache__/models.cpython-312.pyc differ diff --git a/lib/debug.py b/lib/debug.py index 4f922eb69..729a80cb0 100644 --- a/lib/debug.py +++ b/lib/debug.py @@ -1,9 +1,26 @@ -#!/usr/bin/env python3 - +from models import Base, Company, Dev, Freebie from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker + +engine = create_engine('sqlite:///freebies.db') +Session = sessionmaker(bind=engine) +session = Session() + + +Base.metadata.create_all(engine) + +if __name__ == "__main__": + import code + + banner = """ + Debug shell for Freebie Tracker -from models import Company, Dev + Available variables: + - session : SQLAlchemy session + - Company, Dev, Freebie : your models + """ -if __name__ == '__main__': - engine = create_engine('sqlite:///freebies.db') - import ipdb; ipdb.set_trace() + + vars = globals().copy() + vars.update(locals()) + code.interact(banner=banner, local=vars) diff --git a/lib/freebies.db b/lib/freebies.db index 12beb1c96..a7201ee6b 100644 Binary files a/lib/freebies.db and b/lib/freebies.db differ diff --git a/lib/migrations/__pycache__/env.cpython-312.pyc b/lib/migrations/__pycache__/env.cpython-312.pyc new file mode 100644 index 000000000..362a34c2a Binary files /dev/null and b/lib/migrations/__pycache__/env.cpython-312.pyc differ diff --git a/lib/migrations/versions/__pycache__/5f72c58bf48c_create_companies_devs.cpython-312.pyc b/lib/migrations/versions/__pycache__/5f72c58bf48c_create_companies_devs.cpython-312.pyc new file mode 100644 index 000000000..8e790ba3b Binary files /dev/null and b/lib/migrations/versions/__pycache__/5f72c58bf48c_create_companies_devs.cpython-312.pyc differ diff --git a/lib/migrations/versions/__pycache__/7a71dbf71c64_create_db.cpython-312.pyc b/lib/migrations/versions/__pycache__/7a71dbf71c64_create_db.cpython-312.pyc new file mode 100644 index 000000000..f4ddaa6cc Binary files /dev/null and b/lib/migrations/versions/__pycache__/7a71dbf71c64_create_db.cpython-312.pyc differ diff --git a/lib/migrations/versions/__pycache__/aae737829f81_create_freebies_table.cpython-312.pyc b/lib/migrations/versions/__pycache__/aae737829f81_create_freebies_table.cpython-312.pyc new file mode 100644 index 000000000..b221dc731 Binary files /dev/null and b/lib/migrations/versions/__pycache__/aae737829f81_create_freebies_table.cpython-312.pyc differ diff --git a/lib/migrations/versions/aae737829f81_create_freebies_table.py b/lib/migrations/versions/aae737829f81_create_freebies_table.py new file mode 100644 index 000000000..dc527f8e2 --- /dev/null +++ b/lib/migrations/versions/aae737829f81_create_freebies_table.py @@ -0,0 +1,33 @@ +"""Create freebies table + +Revision ID: aae737829f81 +Revises: 5f72c58bf48c +Create Date: 2025-05-25 21:53:21.720728 + +""" +from alembic import op +import sqlalchemy as sa + + + +revision = 'aae737829f81' +down_revision = '5f72c58bf48c' +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + 'freebies', + sa.Column('id', sa.Integer(), primary_key=True), + sa.Column('item_name', sa.String(), nullable=False), + sa.Column('value', sa.Integer(), nullable=False), + sa.Column('dev_id', sa.Integer(), sa.ForeignKey('devs.id')), + sa.Column('company_id', sa.Integer(), sa.ForeignKey('companies.id')) + ) + + + +def downgrade(): + op.drop_table('freebies') + diff --git a/lib/models.py b/lib/models.py index 2681bee5a..b00a81b23 100644 --- a/lib/models.py +++ b/lib/models.py @@ -1,5 +1,5 @@ from sqlalchemy import ForeignKey, Column, Integer, String, MetaData -from sqlalchemy.orm import relationship, backref +from sqlalchemy.orm import relationship from sqlalchemy.ext.declarative import declarative_base convention = { @@ -9,6 +9,7 @@ Base = declarative_base(metadata=metadata) + class Company(Base): __tablename__ = 'companies' @@ -16,14 +17,62 @@ class Company(Base): name = Column(String()) founding_year = Column(Integer()) + freebies = relationship("Freebie", back_populates="company") + + @property + def devs(self): + return list({f.dev for f in self.freebies}) + + def give_freebie(self, dev, item_name, value): + return Freebie(item_name=item_name, value=value, dev=dev, company=self) + + @classmethod + def oldest_company(cls): + from lib.debug import session + return session.query(cls).order_by(cls.founding_year).first() + def __repr__(self): return f'' + class Dev(Base): __tablename__ = 'devs' id = Column(Integer(), primary_key=True) - name= Column(String()) + name = Column(String()) + + freebies = relationship("Freebie", back_populates="dev") + + @property + def companies(self): + return list({f.company for f in self.freebies}) + + def received_one(self, item_name): + return any(f.item_name == item_name for f in self.freebies) + + def give_away(self, dev, freebie): + if freebie in self.freebies: + freebie.dev = dev def __repr__(self): return f'' + + +class Freebie(Base): + __tablename__ = 'freebies' + + id = Column(Integer, primary_key=True) + item_name = Column(String, nullable=False) + value = Column(Integer, nullable=False) + + dev_id = Column(Integer, ForeignKey('devs.id')) + company_id = Column(Integer, ForeignKey('companies.id')) + + dev = relationship("Dev", back_populates="freebies") + company = relationship("Company", back_populates="freebies") + + def print_details(self): + return f"{self.dev.name} owns a {self.item_name} from {self.company.name}" + + def __repr__(self): + return f"" diff --git a/lib/seed.py b/lib/seed.py index b16becbbb..7ec202353 100644 --- a/lib/seed.py +++ b/lib/seed.py @@ -1,3 +1,30 @@ -#!/usr/bin/env python3 +from models import Base, Company, Dev, Freebie +from debug import session -# Script goes here! + +session.query(Freebie).delete() +session.query(Dev).delete() +session.query(Company).delete() +session.commit() + + +company1 = Company(name="TechCorp", founding_year=2001) +company2 = Company(name="DevWorks", founding_year=1999) + + +dev1 = Dev(name="Alice") +dev2 = Dev(name="Bob") + +session.add_all([company1, company2, dev1, dev2]) +session.commit() + + +freebie1 = company1.give_freebie(dev1, "T-Shirt", 25) +freebie2 = company1.give_freebie(dev2, "Sticker Pack", 5) +freebie3 = company2.give_freebie(dev1, "Coffee Mug", 15) + + +session.add_all([freebie1, freebie2, freebie3]) +session.commit() + +print("Seed data created!")