Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Topping Support and Fixed Menu Error #7

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 48 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,22 +70,24 @@ To grab the store menu:
menu = api.get_menu(store)
potato_wedges = menu.get_product_by_name('Potato Wedges')
print(potato_wedges.price)

``get_product_by_name`` is not case sensitive.

**Note**: Never call more than one api function in the same line! This causes
issues with the API that may result in data being incorrectly processed.

.. code:: python

api.add_item_to_basket(item=menu.get_item_by_name("Original Cheese & Tomato"), variant=VARIANTS.MEDIUM)
api.add_item_to_basket(item=menu.get_item_by_name("Original Cheese & Tomato"), variant=VARIANT.MEDIUM)

This code calls two api functions - ``api.add_item_to_basket`` and
``menu.get_item_by_name``. Instead it is recommended to store intermediate
values into separate variables:

.. code:: python

potato_wedges = menu.get_item_by_name('Original Cheese & Tomato')
api.add_item_to_basket(item=potato_wedges, vairant=VARIANT.PERSONAL)
pizza = menu.get_item_by_name('Original Cheese & Tomato')
api.add_item_to_basket(item=pizza, variant=VARIANT.MEDIUM)

Full Usage Example
~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -131,7 +133,7 @@ argument.

This will return a ``Menu`` object that can be search by item name or
alternatively indexed by item ID. The menu item name must be spelled correctly
but is not cases-sensitive. If the item is found io the menu then an ``Item``
but is not cases-sensitive. If the item is found in the menu then an ``Item``
object will be returned which may be added to the basket:

.. code:: python
Expand All @@ -144,7 +146,48 @@ There are four available variants: ``PERSONAL``, ``SMALL``, ``MEDIUM`` and
always be ``PERSONAL``.

By defaut ``add_item_to_basket`` will add only 1 item to the basket at a time
but this may be changed by passing a ``quantity`` argument.
but this may be changed by using a dictionary of ``options``.

.. code:: python

options = {
'quantity': 2,
}
api.add_item_to_basket(pizza, variant=VARIANT.LARGE, options=options)

It is also possible to add extra toppings. In it's current state, the library
offers two ways to add ingredients. You can add by ingredient IDs or names.
To add ingredients by ID, use the ``add_ingredients`` function.

.. code:: python

pizza.add_ingredients(124, 8, 8)

Note that having the same ID twice will give 'Extra' of the topping.
To remove any toppings, simple pass use the ``remove_ingredient`` function
the same way.

To search for toppings by name, you need an ``IngredientList``. This can be
retrieved as follows:

.. code:: python

ingredients = api.get_available_ingredients(pizza, VARIANT.MEDIUM, store)

To search for an ID by the ingredient's name, use ``get_by_name``.

.. code:: python

beef = ingredients.get_by_name("Ground Beef")

To add any number of ingredients by name, you can use a function from
``IngredientList`` called ``add_to_pizza``.

.. code:: python

ingredients.add_to_pizza(pizza, "Ground Beef", "Domino's Stuffed Crust", "Burger Sauce")

None of the ``IngredientList`` functions are case sensitive.

At this time, the Dominos library does not support order placement, although it
should be entirely possible to accept orders that are marked for cash upon
Expand Down
42 changes: 31 additions & 11 deletions dominos/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from backoff import on_exception, expo

from dominos.exception import ApiError
from dominos.models import Stores, Menu, Basket
from dominos.models import Stores, Menu, Basket, IngredientList
from dominos.utils import enum, update_session_headers

import requests
Expand Down Expand Up @@ -126,48 +126,68 @@ def get_basket(self):
response = self.__get('/CheckoutBasket/GetBasket')
return Basket(response.json())

def add_item_to_basket(self, item, variant=VARIANT.MEDIUM, quantity=1):
def get_available_ingredients(self, item, size, store):
"""
Retrieves an IngredientList of ingredients that can be added/removed from the pizza
by name.
:param item: The item to find ingredients for
:param dominos.VARIANT size: The size of the pizza to be ordered
:param store: The store which the order will be placed at
:return: IngredientList: A list of available ingredients
"""
params = {
'isoCode': "en-GB",
'sizeId': size,
'id': item.item_id,
'storeId': store.store_id
}

response = self.__get("/PizzaCustomisation/PizzaViewModelBySize", params=params)
return IngredientList(response.json())

def add_item_to_basket(self, item, variant=VARIANT.MEDIUM, options={'quantity': 1}):
'''
Add an item to the current basket.

:param Item item: Item from menu.
:param int variant: Item SKU id. Ignored if the item is a side.
:param int quantity: The quantity of item to be added.
:param dict options: Dictionary of options like quantity and an ingredients list
:return: A response having added an item to the current basket.
:rtype: requests.Response
'''
item_type = item.type

if item_type == 'Pizza':
return self.add_pizza_to_basket(item, variant, quantity)
return self.add_pizza_to_basket(item, variant, options)
elif item_type == 'Side':
return self.add_side_to_basket(item, quantity)
return self.add_side_to_basket(item, options['quantity'])
return None

def add_pizza_to_basket(self, item, variant=VARIANT.MEDIUM, quantity=1):
def add_pizza_to_basket(self, item, variant=VARIANT.MEDIUM, options={}):
'''
Add a pizza to the current basket.

:param Item item: Item from menu.
:param int variant: Item SKU id. Some defaults are defined in the VARIANT enum.
:param int quantity: The quantity of pizza to be added.
:param dict options: Dictionary of options like quantity and an ingredients list. If nothing is
specified then a default quantity of 1 and the default ingredients for the pizza will be used.
:return: A response having added a pizza to the current basket.
:rtype: requests.Response
'''
item_variant = item[variant]
ingredients = item_variant['ingredients'].update([36, 42])

ingredients = item.ingredients

params = {
'stepId': 0,
'quantity': quantity,
'quantity': options['quantity'],
'sizeId': variant,
'productId': item.item_id,
'ingredients': ingredients,
'productIdHalfTwo': 0,
'ingredientsHalfTwo': [],
'recipeReferrer': 0
}

}
return self.__post('/Basket/AddPizza', json=params)

def add_side_to_basket(self, item, quantity=1):
Expand Down
83 changes: 79 additions & 4 deletions dominos/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
'''
"""
Dominos Pizza API models.
'''
"""
from dominos.utils import strip_unicode_characters
from dominos.exception import ApiError

BASE_URL = 'https://www.dominos.co.uk'
TOPPINGS = ['availableCrusts', 'availableCheeses', 'availableSauces', 'availableToppings']


class Stores(object):
'''
Expand All @@ -28,6 +33,7 @@ def __str__(self):
stores = stores + str(store) + '\n'
return stores


class Store(object):
'''
Encapsulates a single store returned from the API.
Expand All @@ -49,12 +55,24 @@ def __ne__(self, other):
def __str__(self):
return 'name: {}, open: {}'.format(self.name, self.is_open)


class Menu(object):
'''
Encapsulates a store menu.
'''
def make_item(self, item):
"""
Creates either an Item or Pizza class based on the item's type
:param item: The item's data
:param Session api: A Session object to allow interface with Domino's
"""
if item['type'] == "Pizza":
return Pizza(item)
else:
return Item(item)

def __init__(self, data):
self.items = [Item(i) for category in data for i in category['subcategories'][0]['products']]
self.items = [self.make_item(i) for category in data for subcategory in category['subcategories'] for i in subcategory['products']]

def get_product_by_name(self, name):
'''
Expand All @@ -80,9 +98,10 @@ def __str__(self):
menu = menu + str(item) + '\n'
return menu


class Item(object):
'''
Encapsulates a sinlge menu item.
Encapsulates a single menu item.
'''
def __init__(self, data):
self.item_id = data['productId']
Expand All @@ -103,9 +122,65 @@ def __ne__(self, other):
def __str__(self):
return 'name: {}, type: {}, base price: {}'.format(self.name, self.type, self.price)


class Pizza(Item):
"""
Subclass of Item which can encapsulate an ingredient list
"""

def __init__(self, data):
super().__init__(data)
self.ingredients = [36, 42] + self.skus[0]['ingredients']

def add_ingredients(self, *ids):
"""
Adds an ingredient to the pizza
:param id: The ID of the ingredient
"""
self.ingredients += [i for i in ids]

def remove_ingredient(self, id):
"""
Removes an ingredient from the pizza
:param id: The ID of the ingredient
"""
self.ingredients = [x for x in self.ingredients if x != id]


class IngredientList(object):
"""
Encapsulates an available ingredient list
This enables ingredients to be found by name instead of by ID
"""
def __init__(self, data):
data = data['halfOne']
self.toppings = {strip_unicode_characters(x['name'].lower()): x['id'] for c in TOPPINGS for x in data[c]}

def get_by_name(self, name):
"""
Returns an ingredient ID matching the name given
:param name: The name of the ingredient (not case sensitive - must be spelled correctly)
:return id: The ID of the ingredient
"""
try:
return self.toppings[name.lower()]
except KeyError:
raise ApiError("'{}' was not found.".format(name))

def add_to_pizza(self, item, *ingredients):
"""
Adds the ingredients specified to the Pizza given
:param dominos.Pizza item: The item to add ingredients to
:param ingredients: Names of ingredients to add
"""
ids = [self.get_by_name(x) for x in ingredients]
item.add_ingredients(*ids)


class Basket(object):
'''
Encapsulates a basket.
'''
def __init__(self, data):
self.total = data['totalPrice']
self.items = data['items']