├── Chapter05 ├── tests │ ├── __init__.py │ └── test_dependencies │ │ ├── __init__.py │ │ └── test_redis.py ├── temp_messenger │ ├── __init__.py │ ├── dependencies │ │ ├── jinja2.py │ │ └── redis.py │ ├── templates │ │ └── home.html │ └── service.py ├── requirements │ ├── test.in │ ├── base.in │ ├── test.txt │ └── base.txt ├── config.yaml └── readme.md ├── Chapter06 ├── tests │ ├── __init__.py │ ├── test_dependencies │ │ ├── __init__.py │ │ └── test_messages.py │ └── conftest.py ├── temp_messenger │ ├── __init__.py │ ├── templates │ │ ├── login.html │ │ ├── sign_up.html │ │ └── home.html │ ├── user_service.py │ ├── dependencies │ │ ├── jinja2.py │ │ ├── messages.py │ │ └── users.py │ └── message_service.py ├── requirements │ ├── test.in │ ├── base.in │ ├── test.txt │ └── base.txt ├── readme.md ├── setup_db.py └── config.yaml ├── Chapter07 └── gamestore │ ├── main │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0003_auto_20180215_2147.py │ │ ├── 0004_auto_20180215_2156.py │ │ ├── 0002_pricelist.py │ │ ├── 0005_shoppingcart_shoppingcartitem.py │ │ └── 0001_initial.py │ ├── tests.py │ ├── apps.py │ ├── admin.py │ ├── templates │ │ └── main │ │ │ ├── all_games.html │ │ │ ├── highlighted.html │ │ │ ├── signup.html │ │ │ ├── create_account_success.html │ │ │ ├── index.html │ │ │ ├── games-list.html │ │ │ └── cart.html │ ├── urls.py │ ├── forms.py │ └── models.py │ ├── gamestore │ ├── __init__.py │ ├── wsgi.py │ ├── urls.py │ └── settings.py │ ├── static │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ └── images │ │ └── placeholder.png │ ├── package.json │ ├── Pipfile │ ├── package-lock.json │ ├── manage.py │ └── templates │ ├── _loginpartial.html │ ├── login.html │ └── base.html ├── Chapter08 ├── gamestore │ ├── main │ │ ├── __init__.py │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ ├── 0003_auto_20180215_2147.py │ │ │ ├── 0004_auto_20180215_2156.py │ │ │ ├── 0002_pricelist.py │ │ │ ├── 0005_shoppingcart_shoppingcartitem.py │ │ │ └── 0001_initial.py │ │ ├── tests.py │ │ ├── apps.py │ │ ├── admin.py │ │ ├── templates │ │ │ └── main │ │ │ │ ├── all_games.html │ │ │ │ ├── highlighted.html │ │ │ │ ├── signup.html │ │ │ │ ├── create_account_success.html │ │ │ │ ├── index.html │ │ │ │ ├── my-orders.html │ │ │ │ ├── games-list.html │ │ │ │ └── cart.html │ │ ├── urls.py │ │ └── forms.py │ ├── gamestore │ │ ├── __init__.py │ │ ├── wsgi.py │ │ └── urls.py │ ├── static │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ │ └── images │ │ │ └── placeholder.png │ ├── package.json │ ├── Pipfile │ ├── package-lock.json │ ├── manage.py │ └── templates │ │ ├── _loginpartial.html │ │ ├── login.html │ │ └── base.html └── microservices │ ├── order │ ├── main │ │ ├── __init__.py │ │ ├── migrations │ │ │ ├── __init__.py │ │ │ └── 0001_initial.py │ │ ├── apps.py │ │ ├── status.py │ │ ├── admin.py │ │ ├── exceptions.py │ │ ├── urls.py │ │ ├── view_helper.py │ │ ├── models.py │ │ ├── serializers.py │ │ ├── views.py │ │ └── managers.py │ ├── order │ │ ├── __init__.py │ │ ├── wsgi.py │ │ ├── urls.py │ │ └── settings.py │ ├── manage.py │ └── send_order.py │ └── Pipfile ├── Chapter01 ├── weatherterm │ ├── parsers │ │ └── __init__.py │ ├── core │ │ ├── base_enum.py │ │ ├── unit.py │ │ ├── forecast_type.py │ │ ├── set_unit_action.py │ │ ├── __init__.py │ │ ├── request.py │ │ ├── mapper.py │ │ ├── parser_loader.py │ │ ├── unit_converter.py │ │ └── forecast.py │ └── __main__.py └── requirements.txt ├── Chapter09 ├── microservices │ ├── order │ │ ├── main │ │ │ ├── __init__.py │ │ │ ├── migrations │ │ │ │ ├── __init__.py │ │ │ │ └── 0001_initial.py │ │ │ ├── apps.py │ │ │ ├── notification_type.py │ │ │ ├── status.py │ │ │ ├── notifier.py │ │ │ ├── exceptions.py │ │ │ ├── admin.py │ │ │ ├── urls.py │ │ │ ├── view_helper.py │ │ │ ├── models.py │ │ │ ├── serializers.py │ │ │ ├── managers.py │ │ │ └── views.py │ │ ├── order │ │ │ ├── __init__.py │ │ │ ├── wsgi.py │ │ │ └── urls.py │ │ ├── manage.py │ │ └── send_order.py │ └── Pipfile └── notifier │ ├── templates │ ├── order_shipped_template.html │ └── order_received_template.html │ └── Pipfile ├── Chapter04 ├── currency_converter │ ├── config │ │ ├── __init__.py │ │ ├── config_error.py │ │ └── config.py │ ├── core │ │ ├── __init__.py │ │ ├── request.py │ │ ├── currency.py │ │ ├── db.py │ │ ├── actions.py │ │ └── cmdline_parser.py │ └── __main__.py └── Pipfile ├── Chapter02 └── musicterminal │ ├── pytify │ ├── core │ │ ├── exceptions.py │ │ ├── request_type.py │ │ ├── search_type.py │ │ ├── __init__.py │ │ ├── album.py │ │ ├── artist.py │ │ ├── player.py │ │ ├── parameter.py │ │ ├── search.py │ │ ├── request.py │ │ └── config.py │ └── auth │ │ ├── auth_method.py │ │ ├── __init__.py │ │ ├── authorization.py │ │ └── auth.py │ ├── requirements.txt │ ├── client │ ├── empty_results_error.py │ ├── __init__.py │ ├── alignment.py │ ├── menu_item.py │ ├── panel.py │ ├── data_manager.py │ └── menu.py │ ├── templates │ └── index.html │ ├── config.yaml │ ├── spotify_auth.py │ └── app.py ├── Chapter03 ├── core │ ├── twitter │ │ ├── __init__.py │ │ ├── hashtag.py │ │ └── hashtagstats_manager.py │ ├── models │ │ ├── __init__.py │ │ └── models.py │ ├── __init__.py │ ├── app_logger.py │ ├── runner.py │ ├── cmdline_parser.py │ ├── config.py │ └── request.py ├── templates │ └── index.html ├── Pipfile ├── config.yaml ├── logconfig.ini ├── twitter_auth.py └── app.py ├── LICENSE ├── .gitignore └── README.md /Chapter05/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter06/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter05/temp_messenger/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter06/temp_messenger/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter01/weatherterm/parsers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter07/gamestore/gamestore/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter08/gamestore/gamestore/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter05/tests/test_dependencies/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter06/tests/test_dependencies/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/main/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/order/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/order/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter05/requirements/test.in: -------------------------------------------------------------------------------- 1 | pytest 2 | fakeredis 3 | -------------------------------------------------------------------------------- /Chapter06/requirements/test.in: -------------------------------------------------------------------------------- 1 | pytest 2 | fakeredis 3 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/main/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Chapter05/requirements/base.in: -------------------------------------------------------------------------------- 1 | nameko 2 | redis 3 | jinja2 4 | -------------------------------------------------------------------------------- /Chapter01/requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4==4.6.0 2 | selenium==3.6.0 3 | -------------------------------------------------------------------------------- /Chapter04/currency_converter/config/__init__.py: -------------------------------------------------------------------------------- 1 | from .config import get_config -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/core/exceptions.py: -------------------------------------------------------------------------------- 1 | class BadRequestError(Exception): 2 | pass -------------------------------------------------------------------------------- /Chapter02/musicterminal/requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.18.1 2 | Flask==0.12.2 3 | PyYAML==3.12 4 | -------------------------------------------------------------------------------- /Chapter04/currency_converter/config/config_error.py: -------------------------------------------------------------------------------- 1 | class ConfigError(Exception): 2 | pass 3 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/client/empty_results_error.py: -------------------------------------------------------------------------------- 1 | class EmptyResultsError(Exception): 2 | pass -------------------------------------------------------------------------------- /Chapter02/musicterminal/client/__init__.py: -------------------------------------------------------------------------------- 1 | from .menu import Menu 2 | from .data_manager import DataManager -------------------------------------------------------------------------------- /Chapter07/gamestore/main/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /Chapter03/core/twitter/__init__.py: -------------------------------------------------------------------------------- 1 | from .hashtag import Hashtag 2 | from .hashtagstats_manager import HashtagStatsManager -------------------------------------------------------------------------------- /Chapter06/requirements/base.in: -------------------------------------------------------------------------------- 1 | nameko 2 | redis 3 | jinja2 4 | sqlalchemy 5 | nameko-sqlalchemy 6 | bcrypt 7 | flask 8 | psycopg2 9 | -------------------------------------------------------------------------------- /Chapter03/core/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .models import Config 2 | from .models import RequestToken 3 | from .models import RequestAuth 4 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MainConfig(AppConfig): 5 | name = 'main' 6 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MainConfig(AppConfig): 5 | name = 'main' 6 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/main/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MainConfig(AppConfig): 5 | name = 'main' 6 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MainConfig(AppConfig): 5 | name = 'main' 6 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/client/alignment.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | 4 | class Alignment(Enum): 5 | LEFT = auto() 6 | RIGHT = auto() -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/core/request_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | 4 | class RequestType(Enum): 5 | GET = auto() 6 | PUT = auto() -------------------------------------------------------------------------------- /Chapter04/currency_converter/core/__init__.py: -------------------------------------------------------------------------------- 1 | from .request import fetch_exchange_rates_by_currency 2 | from .db import DbClient 3 | from .currency import Currency 4 | -------------------------------------------------------------------------------- /Chapter03/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | Click here to authorize 6 | 7 | -------------------------------------------------------------------------------- /Chapter03/core/twitter/hashtag.py: -------------------------------------------------------------------------------- 1 | class Hashtag: 2 | def __init__(self, name): 3 | self.name = name 4 | self.total = 0 5 | self.refresh_url = None 6 | -------------------------------------------------------------------------------- /Chapter07/gamestore/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter07/gamestore/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /Chapter08/gamestore/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter08/gamestore/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /Chapter02/musicterminal/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Click here to authorize 6 | 7 | -------------------------------------------------------------------------------- /Chapter05/config.yaml: -------------------------------------------------------------------------------- 1 | AMQP_URI: 'pyamqp://guest:guest@localhost' 2 | WEB_SERVER_ADDRESS: '0.0.0.0:8000' 3 | rpc_exchange: 'nameko-rpc' 4 | REDIS_URL: 'redis://localhost:6379/0' 5 | -------------------------------------------------------------------------------- /Chapter07/gamestore/static/images/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter07/gamestore/static/images/placeholder.png -------------------------------------------------------------------------------- /Chapter08/gamestore/static/images/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter08/gamestore/static/images/placeholder.png -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/auth/auth_method.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | 4 | class AuthMethod(Enum): 5 | CLIENT_CREDENTIALS = auto() 6 | AUTHORIZATION_CODE = auto() -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/core/search_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class SearchType(Enum): 5 | ARTIST = 1 6 | ALBUM = 2 7 | PLAYLIST = 3 8 | TRACK = 4 -------------------------------------------------------------------------------- /Chapter03/core/__init__.py: -------------------------------------------------------------------------------- 1 | from .config import read_config 2 | from .request import execute_request 3 | from .cmdline_parser import parse_commandline_args 4 | from .runner import Runner -------------------------------------------------------------------------------- /Chapter07/gamestore/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter07/gamestore/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /Chapter07/gamestore/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter07/gamestore/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /Chapter08/gamestore/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter08/gamestore/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /Chapter08/gamestore/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter08/gamestore/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/notification_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, auto 2 | 3 | 4 | class NotificationType(Enum): 5 | ORDER_RECEIVED = auto() 6 | ORDER_SHIPPED = auto() -------------------------------------------------------------------------------- /Chapter01/weatherterm/core/base_enum.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class BaseEnum(Enum): 5 | 6 | def _generate_next_value_(name, start, count, last_value): 7 | return name 8 | -------------------------------------------------------------------------------- /Chapter03/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | name = "pypi" 4 | verify_ssl = true 5 | url = "https://pypi.python.org/simple" 6 | 7 | 8 | [packages] 9 | 10 | 11 | 12 | [dev-packages] 13 | 14 | -------------------------------------------------------------------------------- /Chapter07/gamestore/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter07/gamestore/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /Chapter07/gamestore/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter07/gamestore/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /Chapter08/gamestore/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter08/gamestore/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /Chapter08/gamestore/static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Python-Programming-Blueprints/HEAD/Chapter08/gamestore/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /Chapter01/weatherterm/core/unit.py: -------------------------------------------------------------------------------- 1 | from enum import auto, unique 2 | from .base_enum import BaseEnum 3 | 4 | 5 | @unique 6 | class Unit(BaseEnum): 7 | CELSIUS = auto() 8 | FAHRENHEIT = auto() 9 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/auth/__init__.py: -------------------------------------------------------------------------------- 1 | from .auth_method import AuthMethod 2 | from .authorization import Authorization 3 | from .auth import authenticate 4 | from .auth import get_auth_key 5 | 6 | -------------------------------------------------------------------------------- /Chapter05/readme.md: -------------------------------------------------------------------------------- 1 | # TempMessenger 2 | 3 | Amend the config to point to your redis instance and rabbitmq instance. 4 | 5 | To run, simply execute: 6 | ``` 7 | nameko run temp_messenger.service --config config.yaml 8 | ``` 9 | -------------------------------------------------------------------------------- /Chapter06/readme.md: -------------------------------------------------------------------------------- 1 | # TempMessenger 2 | 3 | Amend the config to point to your redis instance and rabbitmq instance. 4 | 5 | To run, simply execute: 6 | ``` 7 | nameko run temp_messenger.service --config config.yaml 8 | ``` 9 | -------------------------------------------------------------------------------- /Chapter01/weatherterm/core/forecast_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, unique 2 | 3 | 4 | @unique 5 | class ForecastType(Enum): 6 | TODAY = 'today' 7 | FIVEDAYS = '5day' 8 | TENDAYS = '10day' 9 | WEEKEND = 'weekend' 10 | -------------------------------------------------------------------------------- /Chapter09/notifier/templates/order_shipped_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |Bad log in
11 | {% endif %} 12 | 13 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/core/__init__.py: -------------------------------------------------------------------------------- 1 | from .exceptions import BadRequestError 2 | from .config import read_config 3 | 4 | from .search_type import SearchType 5 | 6 | from .search import search_album 7 | from .search import search_artist 8 | from .search import search_playlist 9 | from .search import search_track 10 | 11 | from .artist import get_artist_albums 12 | from .album import get_album_tracks 13 | from .player import play -------------------------------------------------------------------------------- /Chapter07/gamestore/Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | name = "pypi" 4 | url = "https://pypi.python.org/simple" 5 | verify_ssl = true 6 | 7 | 8 | [packages] 9 | 10 | django = "*" 11 | jedi = "*" 12 | pillow = "*" 13 | requests = "*" 14 | 15 | 16 | [dev-packages] 17 | 18 | jedi = "*" 19 | rope = "*" 20 | importmagic = "*" 21 | "autopep8" = "*" 22 | yapf = "*" 23 | "flake8" = "*" 24 | 25 | 26 | [requires] 27 | 28 | python_version = "3.6" 29 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/templates/main/all_games.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block 'content' %} 4 | 5 |{{ error_message }}
14 | {% endif %} 15 | 16 | -------------------------------------------------------------------------------- /Chapter03/logconfig.ini: -------------------------------------------------------------------------------- 1 | [loggers] 2 | keys=root,twitterVotesLogger 3 | 4 | [handlers] 5 | keys=consoleHandler 6 | 7 | [formatters] 8 | keys=simpleFormatter 9 | 10 | [logger_root] 11 | level=INFO 12 | handlers=consoleHandler 13 | 14 | [logger_twitterVotesLogger] 15 | level=INFO 16 | handlers=consoleHandler 17 | qualname=twitterVotesLogger 18 | 19 | 20 | [handler_consoleHandler] 21 | class=StreamHandler 22 | level=INFO 23 | formatter=simpleFormatter 24 | args=(sys.stdout,) 25 | 26 | [formatter_simpleFormatter] 27 | format=[%(levelname)s] %(asctime)s - %(message)s 28 | datefmt=%Y-%m-%d %H:%M:%S -------------------------------------------------------------------------------- /Chapter07/gamestore/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gamestore", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "bootstrap": { 8 | "version": "3.3.7", 9 | "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.3.7.tgz", 10 | "integrity": "sha1-WjiTlFSfIzMIdaOxUGVldPip63E=" 11 | }, 12 | "font-awesome": { 13 | "version": "4.7.0", 14 | "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", 15 | "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Chapter08/gamestore/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gamestore", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "bootstrap": { 8 | "version": "3.3.7", 9 | "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-3.3.7.tgz", 10 | "integrity": "sha1-WjiTlFSfIzMIdaOxUGVldPip63E=" 11 | }, 12 | "font-awesome": { 13 | "version": "4.7.0", 14 | "resolved": "https://registry.npmjs.org/font-awesome/-/font-awesome-4.7.0.tgz", 15 | "integrity": "sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/client/menu_item.py: -------------------------------------------------------------------------------- 1 | from uuid import uuid1 2 | 3 | 4 | class MenuItem: 5 | def __init__(self, label, data, selected=False): 6 | self.id = str(uuid1()) 7 | self.data = data 8 | self.label = label 9 | 10 | def return_id(): 11 | return self.data['id'], self.data['uri'] 12 | 13 | self.action = return_id 14 | self.selected = selected 15 | 16 | def __eq__(self, other): 17 | return self.id == other.id 18 | 19 | def __len__(self): 20 | return len(self.label) 21 | 22 | def __str__(self): 23 | return self.label -------------------------------------------------------------------------------- /Chapter07/gamestore/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gamestore.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /Chapter08/gamestore/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gamestore.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "order.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /Chapter09/microservices/order/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "order.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /Chapter06/temp_messenger/user_service.py: -------------------------------------------------------------------------------- 1 | from nameko.rpc import rpc 2 | 3 | from .dependencies.users import UserStore 4 | 5 | 6 | class UserService: 7 | 8 | name = 'user_service' 9 | 10 | user_store = UserStore() 11 | 12 | @rpc 13 | def create_user(self, first_name, last_name, email, password): 14 | self.user_store.create( 15 | first_name=first_name, 16 | last_name=last_name, 17 | email=email, 18 | password=password, 19 | ) 20 | 21 | @rpc 22 | def authenticate_user(self, email, password): 23 | self.user_store.authenticate(email, password) 24 | -------------------------------------------------------------------------------- /Chapter07/gamestore/main/migrations/0004_auto_20180215_2156.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.1 on 2018-02-15 21:56 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0003_auto_20180215_2147'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='game', 15 | options={'ordering': ['-highlighted', 'name']}, 16 | ), 17 | migrations.RenameField( 18 | model_name='game', 19 | old_name='promoted', 20 | new_name='highlighted', 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/migrations/0004_auto_20180215_2156.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.0.1 on 2018-02-15 21:56 2 | 3 | from django.db import migrations 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('main', '0003_auto_20180215_2147'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='game', 15 | options={'ordering': ['-highlighted', 'name']}, 16 | ), 17 | migrations.RenameField( 18 | model_name='game', 19 | old_name='promoted', 20 | new_name='highlighted', 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/core/album.py: -------------------------------------------------------------------------------- 1 | from .parameter import prepare_params 2 | from .request import execute_request 3 | 4 | 5 | def get_album_tracks(album_id, auth, params=None): 6 | 7 | if album_id is None or album_id is '': 8 | raise AttributeError( 9 | 'Parameter `album_id` cannot be `None` or empty.') 10 | 11 | url_template = '{base_url}/{area}/{albumid}/{postfix}{query}' 12 | url_params = { 13 | 'query': prepare_params(params), 14 | 'area': 'albums', 15 | 'albumid': album_id, 16 | 'postfix': 'tracks', 17 | } 18 | 19 | return execute_request(url_template, auth, url_params) -------------------------------------------------------------------------------- /Chapter04/currency_converter/config/config.py: -------------------------------------------------------------------------------- 1 | from .config_error import ConfigError 2 | from currency_converter.core import DbClient 3 | 4 | 5 | def get_config(): 6 | config = None 7 | 8 | with DbClient('exchange_rates', 'config') as db: 9 | config = db.find_one() 10 | 11 | if config is None: 12 | error_message = ('It was not possible to get your base currency, that ' 13 | 'probably happened because it have not been ' 14 | 'set yet.\n Please, use the option ' 15 | '--setbasecurrency') 16 | raise ConfigError(error_message) 17 | 18 | return config 19 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/core/artist.py: -------------------------------------------------------------------------------- 1 | from .parameter import prepare_params 2 | from .request import execute_request 3 | 4 | 5 | def get_artist_albums(artist_id, auth, params=None): 6 | 7 | if artist_id is None or artist_id is "": 8 | raise AttributeError( 9 | 'Parameter `artist_id` cannot be `None` or empty.') 10 | 11 | url_template = '{base_url}/{area}/{artistid}/{postfix}{query}' 12 | url_params = { 13 | 'query': prepare_params(params), 14 | 'area': 'artists', 15 | 'artistid': artist_id, 16 | 'postfix': 'albums', 17 | } 18 | 19 | return execute_request(url_template, auth, url_params) -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/notifier.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | 4 | from order import settings 5 | 6 | from .notification_type import NotificationType 7 | 8 | 9 | def notify(order, notification_type): 10 | endpoint = ('notify/order-received/' 11 | if notification_type is NotificationType.ORDER_RECEIVED 12 | else 'notify/order-shipped/') 13 | 14 | header = { 15 | 'X-API-Key': settings.NOTIFIER_API_KEY 16 | } 17 | 18 | response = requests.post( 19 | f'{settings.NOTIFIER_BASEURL}/{endpoint}', 20 | json.dumps(order.data), 21 | headers=header 22 | ) 23 | 24 | return response -------------------------------------------------------------------------------- /Chapter07/gamestore/templates/_loginpartial.html: -------------------------------------------------------------------------------- 1 | {% if user.is_authenticated %} 2 | 16 | 17 | {% else %} 18 | 19 | 22 | 23 | {% endif %} -------------------------------------------------------------------------------- /Chapter08/gamestore/templates/_loginpartial.html: -------------------------------------------------------------------------------- 1 | {% if user.is_authenticated %} 2 | 16 | 17 | {% else %} 18 | 19 | 22 | 23 | {% endif %} -------------------------------------------------------------------------------- /Chapter01/weatherterm/core/request.py: -------------------------------------------------------------------------------- 1 | import os 2 | from selenium import webdriver 3 | 4 | 5 | class Request: 6 | def __init__(self, base_url): 7 | self._driver_path = os.path.join(os.curdir, 'phantomjs/bin/phantomjs') 8 | self._base_url = base_url 9 | self._driver = webdriver.PhantomJS(self._driver_path) 10 | 11 | def fetch_data(self, forecast, area): 12 | url = self._base_url.format(forecast=forecast, area=area) 13 | self._driver.get(url) 14 | 15 | if self._driver.title == '404 Not Found': 16 | error_message = ('Could not find the area that you ' 17 | 'searching for') 18 | raise Exception(error_message) 19 | 20 | return self._driver.page_source 21 | -------------------------------------------------------------------------------- /Chapter08/microservices/order/main/exceptions.py: -------------------------------------------------------------------------------- 1 | class InvalidArgumentError(Exception): 2 | def __init__(self, argument_name): 3 | message = f'The argument {argument_name} is invalid' 4 | super().__init__(message) 5 | 6 | 7 | class OrderAlreadyCompletedError(Exception): 8 | def __init__(self, order): 9 | message = f'The order with ID: {order.id} is already completed.' 10 | super().__init__(message) 11 | 12 | 13 | class OrderAlreadyCancelledError(Exception): 14 | def __init__(self, order): 15 | message = f'The order with ID: {order.id} is already cancelled.' 16 | super().__init__(message) 17 | 18 | 19 | class OrderCancellationError(Exception): 20 | pass 21 | 22 | 23 | class OrderNotFoundError(Exception): 24 | pass 25 | -------------------------------------------------------------------------------- /Chapter04/currency_converter/core/request.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from http import HTTPStatus 3 | import json 4 | 5 | 6 | def fetch_exchange_rates_by_currency(currency): 7 | response = requests.get(f'https://api.fixer.io/latest?base={currency}') 8 | 9 | if response.status_code == HTTPStatus.OK: 10 | return json.loads(response.text) 11 | elif response.status_code == HTTPStatus.NOT_FOUND: 12 | raise ValueError(f'Could not find the exchange rates for: {currency}.') 13 | elif response.status_code == HTTPStatus.BAD_REQUEST: 14 | raise ValueError(f'Invalid base currency value: {currency}') 15 | else: 16 | raise Exception((f'Something went wrong and we were unable to fetch' 17 | f' the exchange rates for: {currency}')) 18 | -------------------------------------------------------------------------------- /Chapter01/weatherterm/core/mapper.py: -------------------------------------------------------------------------------- 1 | class Mapper: 2 | 3 | def __init__(self): 4 | self._mapping = {} 5 | 6 | def _add(self, source, dest): 7 | self._mapping[source] = dest 8 | 9 | def remap_key(self, source, dest): 10 | self._add(source, dest) 11 | 12 | def remap(self, itemslist): 13 | return [self._exec(item) for item in itemslist] 14 | 15 | def _exec(self, src_dict): 16 | dest = dict() 17 | 18 | if not src_dict: 19 | raise AttributeError('The source dictionary can be empty or None') 20 | 21 | for key, value in src_dict.items(): 22 | try: 23 | new_key = self._mapping[key] 24 | dest[new_key] = value 25 | except KeyError: 26 | dest[key] = value 27 | return dest -------------------------------------------------------------------------------- /Chapter09/microservices/order/main/exceptions.py: -------------------------------------------------------------------------------- 1 | class InvalidArgumentError(Exception): 2 | def __init__(self, argument_name): 3 | message = f'The argument {argument_name} is invalid' 4 | super().__init__(message) 5 | 6 | class OrderAlreadyCompletedError(Exception): 7 | def __init__(self, order): 8 | message = f'The order with ID: {order.id} is already completed.' 9 | super(OrderAlreadyCompletedError, self).__init__(message) 10 | 11 | 12 | class OrderAlreadyCancelledError(Exception): 13 | def __init__(self, order): 14 | message = f'The order with ID: {order.id} is already cancelled.' 15 | super(OrderAlreadyCancelledError, self).__init__(message) 16 | 17 | 18 | class OrderCancellationError(Exception): 19 | pass 20 | 21 | 22 | class OrderNotFoundError(Exception): 23 | pass -------------------------------------------------------------------------------- /Chapter03/core/runner.py: -------------------------------------------------------------------------------- 1 | import concurrent.futures 2 | 3 | from rx import Observable 4 | 5 | 6 | class Runner: 7 | 8 | def __init__(self, on_success, on_error, on_complete): 9 | self._on_success = on_success 10 | self._on_error = on_error 11 | self._on_complete = on_complete 12 | 13 | def exec(self, func, items): 14 | observables = [] 15 | 16 | with concurrent.futures.ProcessPoolExecutor() as executor: 17 | for item in items.values(): 18 | _future = executor.submit(func, item) 19 | observables.append(Observable.from_future(_future)) 20 | 21 | all_observables = Observable.merge(observables) 22 | 23 | all_observables.subscribe(self._on_success, 24 | self._on_error, 25 | self._on_complete) 26 | -------------------------------------------------------------------------------- /Chapter07/gamestore/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block 'content' %} 4 | 5 |8 |
| Item | 14 |Quantity | 15 |Price per unit | 16 |
|---|---|---|
| {{item.name}} | 22 |{{item.quantity}} | 23 |${{item.price_per_unit}} | 24 |
14 | See more items 15 |
16 | {% endif %} 17 |25 | See all items 26 |
27 | {% endif %} 28 | {% endif %} 29 | 30 | {% endblock %} -------------------------------------------------------------------------------- /Chapter08/gamestore/main/templates/main/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block 'content' %} 4 | {% if highlighted_games_list %} 5 |14 | See more items 15 |
16 | {% endif %} 17 |25 | See all items 26 |
27 | {% endif %} 28 | {% endif %} 29 | 30 | {% endblock %} -------------------------------------------------------------------------------- /Chapter01/weatherterm/core/parser_loader.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import inspect 4 | 5 | 6 | def _get_parser_list(dirname): 7 | files = [f.replace('.py', '') 8 | for f in os.listdir(dirname) 9 | if not f.startswith('__')] 10 | return files 11 | 12 | 13 | def _import_parsers(parserfiles): 14 | m = re.compile('.+parser$', re.I) 15 | _modules = __import__('weatherterm.parsers', 16 | globals(), 17 | locals(), 18 | parserfiles, 19 | 0) 20 | 21 | _parsers = [(k, v) for k, v in inspect.getmembers(_modules) 22 | if inspect.ismodule(v) and m.match(k)] 23 | _classes = dict() 24 | 25 | for k, v in _parsers: 26 | _classes.update({k: v for k, v in inspect.getmembers(v) 27 | if inspect.isclass(v) and m.match(k)}) 28 | return _classes 29 | 30 | 31 | def load(dirname): 32 | parserfiles = _get_parser_list(dirname) 33 | return _import_parsers(parserfiles) 34 | -------------------------------------------------------------------------------- /Chapter02/musicterminal/pytify/core/search.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | 4 | from .search_type import SearchType 5 | from pytify.core import read_config 6 | 7 | 8 | def _search(criteria, auth, search_type): 9 | 10 | conf = read_config() 11 | 12 | if not criteria: 13 | raise AttributeError('Parameter `criteria` is required.') 14 | 15 | q_type = search_type.name.lower() 16 | url = f'{conf.base_url}/search?q={criteria}&type={q_type}' 17 | 18 | headers = {'Authorization': f'Bearer {auth.access_token}'} 19 | response = requests.get(url, headers=headers) 20 | 21 | return json.loads(response.text) 22 | 23 | 24 | def search_artist(criteria, auth): 25 | return _search(criteria, auth, SearchType.ARTIST) 26 | 27 | 28 | def search_album(criteria, auth): 29 | return _search(criteria, auth, SearchType.ALBUM) 30 | 31 | 32 | def search_playlist(criteria, auth): 33 | return _search(criteria, auth, SearchType.PLAYLIST) 34 | 35 | 36 | def search_track(criteria, auth): 37 | return _search(criteria, auth, SearchType.TRACK) -------------------------------------------------------------------------------- /Chapter02/musicterminal/client/panel.py: -------------------------------------------------------------------------------- 1 | import curses 2 | import curses.panel 3 | from uuid import uuid1 4 | 5 | 6 | class Panel: 7 | 8 | def __init__(self, title, dimensions): 9 | height, width, y, x = dimensions 10 | 11 | self._win = curses.newwin(height, width, y, x) 12 | self._win.box() 13 | self._panel = curses.panel.new_panel(self._win) 14 | self.title = title 15 | self._id = uuid1() 16 | 17 | self._set_title() 18 | 19 | self.hide() 20 | 21 | def hide(self): 22 | self._panel.hide() 23 | 24 | def _set_title(self): 25 | formatted_title = f' {self.title} ' 26 | self._win.addstr(0, 2, formatted_title, curses.A_REVERSE) 27 | 28 | def show(self): 29 | self._win.clear() 30 | self._win.box() 31 | self._set_title() 32 | curses.curs_set(0) 33 | self._panel.show() 34 | 35 | def is_visible(self): 36 | return not self._panel.hidden() 37 | 38 | def __eq__(self, other): 39 | return self._id == other._id 40 | 41 | -------------------------------------------------------------------------------- /Chapter05/requirements/base.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --output-file requirements/base.txt requirements/base.in 6 | # 7 | amqp==1.4.9 # via kombu 8 | anyjson==0.3.3 # via kombu 9 | certifi==2018.1.18 # via requests 10 | chardet==3.0.4 # via requests 11 | enum-compat==0.0.2 # via eventlet 12 | eventlet==0.21.0 # via nameko 13 | greenlet==0.4.13 # via eventlet 14 | idna==2.6 # via requests 15 | jinja2==2.10 16 | kombu==3.0.37 # via nameko 17 | markupsafe==1.0 # via jinja2 18 | mock==2.0.0 # via nameko 19 | nameko==2.8.3 20 | path.py==10.5 # via nameko 21 | pbr==3.1.1 # via mock 22 | pyyaml==3.12 # via nameko 23 | redis==2.10.6 24 | requests==2.18.4 # via nameko 25 | six==1.11.0 # via mock, nameko 26 | urllib3==1.22 # via requests 27 | werkzeug==0.14.1 # via nameko 28 | wrapt==1.10.11 # via nameko 29 | -------------------------------------------------------------------------------- /Chapter04/currency_converter/core/currency.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Currency(Enum): 5 | AUD = 'Australia Dollar' 6 | BGN = 'Bulgaria Lev' 7 | BRL = 'Brazil Real' 8 | CAD = 'Canada Dollar' 9 | CHF = 'Switzerland Franc' 10 | CNY = 'China Yuan/Renminbi' 11 | CZK = 'Czech Koruna' 12 | DKK = 'Denmark Krone' 13 | GBP = 'Great Britain Pound' 14 | HKD = 'Hong Kong Dollar' 15 | HRK = 'Croatia Kuna' 16 | HUF = 'Hungary Forint' 17 | IDR = 'Indonesia Rupiah' 18 | ILS = 'Israel New Shekel' 19 | INR = 'India Rupee' 20 | JPY = 'Japan Yen' 21 | KRW = 'South Korea Won' 22 | MXN = 'Mexico Peso' 23 | MYR = 'Malaysia Ringgit' 24 | NOK = 'Norway Kroner' 25 | NZD = 'New Zealand Dollar' 26 | PHP = 'Philippines Peso' 27 | PLN = 'Poland Zloty' 28 | RON = 'Romania New Lei' 29 | RUB = 'Russia Rouble' 30 | SEK = 'Sweden Krona' 31 | SGD = 'Singapore Dollar' 32 | THB = 'Thailand Baht' 33 | TRY = 'Turkish New Lira' 34 | USD = 'USA Dollar' 35 | ZAR = 'South Africa Rand' 36 | EUR = 'Euro' 37 | -------------------------------------------------------------------------------- /Chapter08/gamestore/main/templates/main/my-orders.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block 'content' %} 4 | 5 || Product name | 18 |Quantity | 19 |Price per unit | 20 |
|---|---|---|
| {{item.name}} | {{item.quantity}} | 26 |${{item.price_per_unit}} | 27 |
Release year: {{game.release_year}}
18 |Developer: {{game.developer}}
19 |Publisher: {{game.published_by}}
20 | {% if game.pricelist.price_per_unit %} 21 |22 | Price: ${{game.pricelist.price_per_unit|floatformat:2|intcomma}} 23 |
24 | {% else %} 25 |Price: Not available
26 | {% endif %} 27 |Release year: {{game.release_year}}
18 |Developer: {{game.developer}}
19 |Publisher: {{game.published_by}}
20 | {% if game.pricelist.price_per_unit %} 21 |22 | Price: ${{game.pricelist.price_per_unit|floatformat:2|intcomma}} 23 |
24 | {% else %} 25 |Price: Not available
26 | {% endif %} 27 |