diff --git a/.coveragerc b/.coveragerc index 0f49c0ea..312760f8 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,11 +1,4 @@ [run] -branch = true - -[report] -omit = - *site-packages* - *tests* - *.tox* -show_missing = True -exclude_lines = - raise NotImplementedError +branch = True +include = longclaw/* +omit = */migrations/*,*/test/*,*/bin/*,*/docs/*,*/tests/*,*/__init__.py diff --git a/.dockerignore b/.dockerignore deleted file mode 100755 index 27d2dae2..00000000 --- a/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -*/node_modules -*.log diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 585e2abc..00000000 --- a/.editorconfig +++ /dev/null @@ -1,23 +0,0 @@ -# http://editorconfig.org - -root = true - -[*] -charset = utf-8 -end_of_line = lf -insert_final_newline = true -trim_trailing_whitespace = true - -[*.{py,rst,ini}] -indent_style = space -indent_size = 4 - -[*.{html,css,scss,json,yml}] -indent_style = space -indent_size = 2 - -[*.md] -trim_trailing_whitespace = false - -[Makefile] -indent_style = tab diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..b5427fd4 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,37 @@ +name: Tests + +on: + push: + branches: + - main + - 'stable/**' + + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + + test-python: + runs-on: ubuntu-latest + strategy: + matrix: + python: ["3.8", "3.9", "3.10", "3.11"] + + steps: + - uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + python -m pip install tox tox-gh-actions + - name: Test with tox + run: tox diff --git a/.gitignore b/.gitignore index 558440e9..cc612df6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,56 +1,163 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python +# Edit at https://www.toptal.com/developers/gitignore?templates=python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ *.py[cod] -__pycache__ -.directory -.vscode/ -tags -node_modules/ +*$py.class # C extensions *.so -# Packages -*.egg -*.egg-info -dist -build -eggs -parts -var -sdist -develop-eggs +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ .installed.cfg -lib -lib64 +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec # Installer logs pip-log.txt +pip-delete-this-directory.txt # Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ .coverage -.tox +.coverage.* +.cache nosetests.xml coverage.xml -htmlcov +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ # Translations *.mo +*.pot + +# Django stuff: +*.log +local_settings.py +*.sqlite3 +*.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py -# Mr Developer -.mr.developer.cfg -.project -.pydevproject +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version -# Pycharm/Intellij -.idea +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock -# Complexity -output/*.html -output/*/index.html +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock -# Sphinx -docs/_build +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ -webpack-stats.json -*bundle.js* -.eggs/ \ No newline at end of file +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# local +poetry.lock +node_modules/ +/tempkeep +/vagrant diff --git a/.landscape.yaml b/.landscape.yaml deleted file mode 100644 index 5f4be7bb..00000000 --- a/.landscape.yaml +++ /dev/null @@ -1,16 +0,0 @@ -doc-warnings: true -max-line-length: 160 -uses: - - django -ignore-paths: - - longclaw/basket/migrations - - longclaw/checkout/migrations - - longclaw/core/migrations - - longclaw/orders/migrations - - longclaw/products/migrations - - longclaw/settings/migrations - - longclaw/shipping/migrations -python-targets: - - 3 - - diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..56c967ed --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,13 @@ +repos: + - repo: https://github.com/pycqa/isort + rev: 5.10.1 + hooks: + - id: isort + - repo: https://github.com/psf/black + rev: 22.10.0 + hooks: + - id: black + - repo: https://github.com/pycqa/flake8 + rev: 5.0.4 + hooks: + - id: flake8 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index cd3ada21..00000000 --- a/.travis.yml +++ /dev/null @@ -1,32 +0,0 @@ -# Config file for automatic testing at travis-ci.org - -language: python -cache: pip - -matrix: - include: - - env: TOX_ENV=py35-django-225 - python: 3.5 - - env: TOX_ENV=py36-django-225 - python: 3.6 - - env: TOX_ENV=py37-django-225 - python: 3.7 - dist: xenial - sudo: true -# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors -install: -- . $HOME/.nvm/nvm.sh -- nvm install stable -- nvm use stable -- pip install -r requirements_dev.txt -- cd longclaw/client && npm install - -# command to run tests using coverage, e.g. python setup.py test -script: tox -e $TOX_ENV - -after_success: - - codecov -e TOX_ENV - - git config --global user.name "${GH_NAME}" - - git config --global user.email "${GH_EMAIL}" - - echo "machine github.com login ${GH_NAME} password ${GH_TOKEN}" > ~/.netrc - - cd website && npm install && GIT_USER="${GH_NAME}" npm run publish-gh-pages \ No newline at end of file diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ab1418c1..3dcb1974 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,10 @@ History ------- +Unreleased + +# This work to re-boot the project and modernise codebase + 1.0.0 +++++++++++ diff --git a/Dockerfile b/Dockerfile deleted file mode 100755 index d369844d..00000000 --- a/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM node:8.11.4 - -WORKDIR /app/website - -EXPOSE 3000 35729 -COPY ./docs /app/docs -COPY ./website /app/website -RUN yarn install - -CMD ["yarn", "start"] diff --git a/Dockerfile.dev b/Dockerfile.dev deleted file mode 100644 index 61578444..00000000 --- a/Dockerfile.dev +++ /dev/null @@ -1,10 +0,0 @@ -# Dockerfile -FROM python:3.7 -RUN mkdir /web -WORKDIR /web -COPY setup.py requirements_dev.txt requirements.txt /web/ -RUN pip install -r requirements_dev.txt -COPY . /web/ - -EXPOSE 8001 -CMD ["python", "manage.py", "runserver", "0.0.0.0:8001"] \ No newline at end of file diff --git a/Makefile b/Makefile index 18933ea4..efec75a3 100644 --- a/Makefile +++ b/Makefile @@ -1,59 +1,23 @@ -.PHONY: clean-pyc clean-build docs help -.DEFAULT_GOAL := help -define BROWSER_PYSCRIPT -import os, webbrowser, sys -try: - from urllib import pathname2url -except: - from urllib.request import pathname2url +migrate: + @python testmanage.py migrate -webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) -endef -export BROWSER_PYSCRIPT -BROWSER := python -c "$$BROWSER_PYSCRIPT" +admin: + @echo "Creating superuser" + @echo "from django.contrib.auth import get_user_model; get_user_model().objects.create_superuser('test', '', 'test')" | python testmanage.py shell -help: - @perl -nle'print $& if m{^[a-zA-Z_-]+:.*?## .*$$}' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-25s\033[0m %s\n", $$1, $$2}' +run: + @python testmanage.py runserver 0.0.0.0:8000 -clean: clean-build clean-pyc +test: + @echo "Running tests..." + @coverage run testmanage.py test --deprecation all && coverage report -m -clean-build: ## remove build artifacts - rm -fr build/ - rm -fr dist/ - rm -fr *.egg-info +setup: + @echo "Setting up test environment..." + @pip install -e ".[testing,dev]" + @make migrate + @make admin -clean-pyc: ## remove Python file artifacts - find . -name '*.pyc' -exec rm -f {} + - find . -name '*.pyo' -exec rm -f {} + - find . -name '*~' -exec rm -f {} + - -lint: ## check style with flake8 - flake8 longclaw tests - -test: ## run tests quickly with the default Python - python runtests.py tests - -test-all: ## run tests on every Python version with tox - tox - -coverage: ## check code coverage quickly with the default Python - coverage run --source longclaw runtests.py tests - coverage report -m - coverage html - open htmlcov/index.html - -docs: ## generate Sphinx HTML documentation, including API docs - rm -f docs/longclaw.rst - rm -f docs/modules.rst - sphinx-apidoc -o docs/ longclaw - $(MAKE) -C docs clean - $(MAKE) -C docs html - $(BROWSER) docs/_build/html/index.html - -release: clean ## package and upload a release - python setup.py sdist upload - python setup.py bdist_wheel upload - -sdist: clean ## package - python setup.py sdist - ls -l dist +countries: + @echo "Loading countries..." + @python testmanage.py loadcountries diff --git a/README.md b/README.md index 310149d9..61e9e2d0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,59 @@ # Longclaw +## Working with Wagtail 4.1 + +### Temporary developer notes + +This is a rework of the Longclaw master branch that adjusts the code for working with Wagtail 2.15+ but makes a few other changes, for now, to hopefully re-boot a Wagtail package that always looked so promising. + +I wanted to use this for client work some time back but could not quite get it working but did use a lot of ideas from it in those websites. + +## What is fixed + +It's opinionated but... + +- It requires Wagtail 2.15+ (LTS) and Django 3.2+ (LTS) +- The original tests run OK. +- Tox is modernised and runs in github actions, along with coverage. +- I think I have made the test app work better for a developer. +- Developer setup is improved without been too different. +- The admin site works well, especially nice with Wagtail 4.1 +- The orders Dashboard is moved to it's own admin page, at least for the moment. + +## What not fixed / removed / broken + +Although I haven't included some original code there's no reason It couldn't be added back in. + +- Not fixed, frontend client side CSS/JS +- The templates could do with some work. +- The project_template isn't included now. +- The documentation needs attention, mainly to update it to the latest Wagtail version. +- Shopping basket +- Checkout +- And may be something else you see and fancy getting involved in! + +## Setup + +First create a virtual environment, any how you like and activate it. + +```bash +git clone git@github.com:nickmoreton/longclaw.git +git switch modernise-integration +make setup +make countries +make run +``` + +View the test app home page + +View the wagtail admin. User: `test` Password: `test` + +See the Makefile for some more developer commands. + +Once logged in add a Product Index page and some products + +--- + ## Call For Maintainers If you are interested in developing this project, please get in touch diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index 764d31ab..00000000 --- a/codecov.yml +++ /dev/null @@ -1,2 +0,0 @@ -codecov: - token: c4e276fe-1d69-49e9-bbbe-fabaf4890222 \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100755 index 6711192a..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,18 +0,0 @@ -version: "3" - -services: - docusaurus: - build: . - ports: - - 3000:3000 - - 35729:35729 - volumes: - - ./docs:/app/docs - - ./website/blog:/app/website/blog - - ./website/core:/app/website/core - - ./website/i18n:/app/website/i18n - - ./website/pages:/app/website/pages - - ./website/static:/app/website/static - - ./website/sidebars.json:/app/website/sidebars.json - - ./website/siteConfig.js:/app/website/siteConfig.js - working_dir: /app/website diff --git a/longclaw/__init__.py b/longclaw/__init__.py index 5e823261..7863915f 100644 --- a/longclaw/__init__.py +++ b/longclaw/__init__.py @@ -1,2 +1 @@ - __version__ = "1.0.2" diff --git a/longclaw/basket/__init__.py b/longclaw/basket/__init__.py index e69de29b..9a715b7d 100644 --- a/longclaw/basket/__init__.py +++ b/longclaw/basket/__init__.py @@ -0,0 +1 @@ +# default_app_config = "longclaw.basket.apps.LongclawBasketConfig" diff --git a/longclaw/basket/api.py b/longclaw/basket/api.py index d2cee411..f8a3a225 100644 --- a/longclaw/basket/api.py +++ b/longclaw/basket/api.py @@ -1,9 +1,10 @@ -from rest_framework.decorators import action from rest_framework import permissions, status, viewsets +from rest_framework.decorators import action from rest_framework.response import Response + +from longclaw.basket import utils from longclaw.basket.models import BasketItem from longclaw.basket.serializers import BasketItemSerializer -from longclaw.basket import utils from longclaw.utils import ProductVariant from .signals import basket_modified @@ -14,8 +15,9 @@ class BasketViewSet(viewsets.ModelViewSet): Viewset for interacting with a sessions 'basket' - ``ProductVariants`` which have been marked for checkout.BaseException """ + serializer_class = BasketItemSerializer - permission_classes = (permissions.AllowAny, ) + permission_classes = (permissions.AllowAny,) def get_queryset(self, request=None): items, _ = utils.get_basket_items(request or self.request) @@ -45,15 +47,14 @@ def create(self, request): item.save() serializer = BasketItemSerializer(self.get_queryset(request), many=True) - response = Response(data=serializer.data, - status=status.HTTP_201_CREATED) - + response = Response(data=serializer.data, status=status.HTTP_201_CREATED) + basket_modified.send(sender=BasketItem, basket_id=bid) else: response = Response( - {"message": "Missing 'variant_id'"}, - status=status.HTTP_400_BAD_REQUEST) + {"message": "Missing 'variant_id'"}, status=status.HTTP_400_BAD_REQUEST + ) return response @@ -69,11 +70,10 @@ def bulk_update(self, request): item.save() serializer = BasketItemSerializer(self.get_queryset(request), many=True) - response = Response(data=serializer.data, - status=status.HTTP_200_OK) - + response = Response(data=serializer.data, status=status.HTTP_200_OK) + basket_modified.send(sender=BasketItem, basket_id=bid) - + return response def destroy(self, request, variant_id=None): @@ -81,25 +81,23 @@ def destroy(self, request, variant_id=None): Remove an item from the basket """ bid = utils.basket_id(request) - + variant = ProductVariant.objects.get(id=variant_id) quantity = int(request.data.get("quantity", 1)) try: - item = BasketItem.objects.get( - basket_id=bid, variant=variant) + item = BasketItem.objects.get(basket_id=bid, variant=variant) item.decrease_quantity(quantity) except BasketItem.DoesNotExist: pass serializer = BasketItemSerializer(self.get_queryset(request), many=True) - response = Response(data=serializer.data, - status=status.HTTP_200_OK) - + response = Response(data=serializer.data, status=status.HTTP_200_OK) + basket_modified.send(sender=BasketItem, basket_id=bid) - - return response - @action(detail=False, methods=['get']) + return response + + @action(detail=False, methods=["get"]) def total_items(self, request): """ Get total number of items in the basket @@ -110,7 +108,7 @@ def total_items(self, request): return Response(data={"quantity": n_total}, status=status.HTTP_200_OK) - @action(detail=True, methods=['get']) + @action(detail=True, methods=["get"]) def item_count(self, request, variant_id=None): """ Get quantity of a single item in the basket diff --git a/longclaw/basket/apps.py b/longclaw/basket/apps.py index 456a4478..024c2bbd 100644 --- a/longclaw/basket/apps.py +++ b/longclaw/basket/apps.py @@ -1,5 +1,8 @@ from django.apps import AppConfig -class basketConfig(AppConfig): - name = 'longclaw.basket' +class LongclawBasketConfig(AppConfig): + label = "longclaw_basket" + name = "longclaw.basket" + verbose_name = "Longclaw Basket" + default_auto_field = "django.db.models.AutoField" diff --git a/longclaw/basket/context_processors.py b/longclaw/basket/context_processors.py index 8e7ce362..7ace63e8 100644 --- a/longclaw/basket/context_processors.py +++ b/longclaw/basket/context_processors.py @@ -1,5 +1,6 @@ -from longclaw import settings # import the settings file +from longclaw import settings # import the settings file + def stripe_key(request): # return the value you want as a dictionnary. you may add multiple values in there. - return {'STRIPE_KEY': settings.STRIPE_PUBLISHABLE} + return {"STRIPE_KEY": settings.STRIPE_PUBLISHABLE} diff --git a/longclaw/basket/forms.py b/longclaw/basket/forms.py index 836d9af5..b70f0773 100644 --- a/longclaw/basket/forms.py +++ b/longclaw/basket/forms.py @@ -7,12 +7,11 @@ class AddToBasketForm(forms.Form): variant_ref = forms.CharField(widget=forms.HiddenInput()) def __init__(self, request=None, *args, **kwargs): - self.request=request + self.request = request super(AddToBasketForm, self).__init__(*args, **kwargs) def clean(self): - """ Check user has cookies enabled - """ + """Check user has cookies enabled""" if self.request: if not self.request.session.test_cookie_worked(): raise forms.ValidationError("Cookies must be enabled.") diff --git a/longclaw/basket/jinja2tags.py b/longclaw/basket/jinja2tags.py index 8807f70e..f2bffa62 100644 --- a/longclaw/basket/jinja2tags.py +++ b/longclaw/basket/jinja2tags.py @@ -1,33 +1,33 @@ import jinja2 -from jinja2 import nodes -from jinja2.ext import Extension - from django.template.loader import get_template +from jinja2.ext import Extension from .templatetags.basket_tags import get_basket_items -from .utils import get_basket_items + +# from .utils import get_basket_items -def add_to_basket_btn(variant_id, btn_class="btn btn-default", btn_text="Add To Basket"): - """Button to add an item to the basket - """ - basket_template = get_template('basket/add_to_basket.html') +def add_to_basket_btn( + variant_id, btn_class="btn btn-default", btn_text="Add To Basket" +): + """Button to add an item to the basket""" + basket_template = get_template("basket/add_to_basket.html") - return basket_template.render(context={ - 'btn_class': btn_class, - 'variant_id': variant_id, - 'btn_text': btn_text - }) + return basket_template.render( + context={"btn_class": btn_class, "variant_id": variant_id, "btn_text": btn_text} + ) class LongClawBasketExtension(Extension): def __init__(self, environment): super(LongClawBasketExtension, self).__init__(environment) - self.environment.globals.update({ - 'basket': jinja2.contextfunction(get_basket_items), - 'add_to_basket_btn': add_to_basket_btn, - }) + self.environment.globals.update( + { + "basket": jinja2.contextfunction(get_basket_items), + "add_to_basket_btn": add_to_basket_btn, + } + ) # Nicer import names diff --git a/longclaw/basket/management/commands/remove_stale_baskets.py b/longclaw/basket/management/commands/remove_stale_baskets.py index e7070c3e..5d256f09 100644 --- a/longclaw/basket/management/commands/remove_stale_baskets.py +++ b/longclaw/basket/management/commands/remove_stale_baskets.py @@ -1,20 +1,24 @@ import datetime + from django.core.management import BaseCommand + from longclaw.basket.models import BasketItem + class Command(BaseCommand): """Remove old BasketItems. This command can be used in conjunction with e.g. a cron job to stop your database being polluted with abandoned basket items. """ + help = "Remove baskets older than the given number of days" def add_arguments(self, parser): - parser.add_argument('older_than_days', type=int) + parser.add_argument("older_than_days", type=int) # A command must define handle() def handle(self, *args, **options): - days_old = options['older_than_days'] + days_old = options["older_than_days"] today = datetime.date.today() date = today - datetime.timedelta(days=days_old) diff --git a/longclaw/basket/migrations/0001_initial.py b/longclaw/basket/migrations/0001_initial.py index da86d2f9..6b51630c 100644 --- a/longclaw/basket/migrations/0001_initial.py +++ b/longclaw/basket/migrations/0001_initial.py @@ -1,8 +1,8 @@ # Generated by Django 2.1.4 on 2018-12-22 14:47 -from django.db import migrations, models import django.db.models.deletion from django.conf import settings +from django.db import migrations, models class Migration(migrations.Migration): @@ -10,21 +10,35 @@ class Migration(migrations.Migration): initial = True dependencies = [ - (settings.PRODUCT_VARIANT_MODEL.split(".")[0], '__first__'), + (settings.PRODUCT_VARIANT_MODEL.split(".")[0], "__first__"), ] operations = [ migrations.CreateModel( - name='BasketItem', + name="BasketItem", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('basket_id', models.CharField(max_length=32)), - ('date_added', models.DateTimeField(auto_now_add=True)), - ('quantity', models.IntegerField(default=1)), - ('variant', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.PRODUCT_VARIANT_MODEL)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("basket_id", models.CharField(max_length=32)), + ("date_added", models.DateTimeField(auto_now_add=True)), + ("quantity", models.IntegerField(default=1)), + ( + "variant", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to=settings.PRODUCT_VARIANT_MODEL, + ), + ), ], options={ - 'ordering': ['date_added'], + "ordering": ["date_added"], }, ), ] diff --git a/longclaw/basket/models.py b/longclaw/basket/models.py index 2505368d..a9ef5241 100644 --- a/longclaw/basket/models.py +++ b/longclaw/basket/models.py @@ -1,14 +1,18 @@ from django.db import models + from longclaw.settings import PRODUCT_VARIANT_MODEL + class BasketItem(models.Model): basket_id = models.CharField(max_length=32) date_added = models.DateTimeField(auto_now_add=True) quantity = models.IntegerField(default=1) - variant = models.ForeignKey(PRODUCT_VARIANT_MODEL, unique=False, on_delete=models.PROTECT) + variant = models.ForeignKey( + PRODUCT_VARIANT_MODEL, unique=False, on_delete=models.PROTECT + ) class Meta: - ordering = ['date_added'] + ordering = ["date_added"] def __str__(self): return "{}x {}".format(self.quantity, self.variant) @@ -23,14 +27,12 @@ def price(self): return self.variant.price def increase_quantity(self, quantity=1): - """ Increase the quantity of this product in the basket - """ + """Increase the quantity of this product in the basket""" self.quantity += quantity self.save() def decrease_quantity(self, quantity=1): - """ - """ + """ """ self.quantity -= quantity if self.quantity <= 0: self.delete() diff --git a/longclaw/basket/serializers.py b/longclaw/basket/serializers.py index 987e1257..84d58f35 100644 --- a/longclaw/basket/serializers.py +++ b/longclaw/basket/serializers.py @@ -1,7 +1,8 @@ from rest_framework import serializers -from longclaw.products.serializers import ProductVariantSerializer from longclaw.basket.models import BasketItem +from longclaw.products.serializers import ProductVariantSerializer + class BasketItemSerializer(serializers.ModelSerializer): @@ -18,4 +19,3 @@ def get_price(self, obj): def get_total(self, obj): return obj.total() - \ No newline at end of file diff --git a/longclaw/basket/signals.py b/longclaw/basket/signals.py index 9ecb6d9b..74850cdf 100644 --- a/longclaw/basket/signals.py +++ b/longclaw/basket/signals.py @@ -1,3 +1,3 @@ import django.dispatch -basket_modified = django.dispatch.Signal(providing_args=['basket_id']) +basket_modified = django.dispatch.Signal("basket_id") diff --git a/longclaw/basket/templatetags/basket_tags.py b/longclaw/basket/templatetags/basket_tags.py index 27835498..8833155c 100644 --- a/longclaw/basket/templatetags/basket_tags.py +++ b/longclaw/basket/templatetags/basket_tags.py @@ -1,8 +1,10 @@ from django import template + from longclaw.basket.utils import get_basket_items register = template.Library() + @register.simple_tag(takes_context=True) def basket(context): """ @@ -12,12 +14,9 @@ def basket(context): return items -@register.inclusion_tag('basket/add_to_basket.html') -def add_to_basket_btn(variant_id, btn_class="btn btn-default", btn_text="Add To Basket"): - """Button to add an item to the basket - """ - return { - 'btn_class': btn_class, - 'variant_id': variant_id, - 'btn_text': btn_text - } +@register.inclusion_tag("basket/add_to_basket.html") +def add_to_basket_btn( + variant_id, btn_class="btn btn-default", btn_text="Add To Basket" +): + """Button to add an item to the basket""" + return {"btn_class": btn_class, "variant_id": variant_id, "btn_text": btn_text} diff --git a/longclaw/basket/urls.py b/longclaw/basket/urls.py index e6ddcbe6..908aab46 100644 --- a/longclaw/basket/urls.py +++ b/longclaw/basket/urls.py @@ -1,42 +1,34 @@ -from django.conf.urls import url -from longclaw.basket import api -from longclaw.basket import views +from django.urls import path + +from longclaw.basket import api, views from longclaw.settings import API_URL_PREFIX -basket_list = api.BasketViewSet.as_view({ - 'get': 'list', - 'post': 'create', - 'put': 'bulk_update' -}) +basket_list = api.BasketViewSet.as_view( + {"get": "list", "post": "create", "put": "bulk_update"} +) -basket_detail = api.BasketViewSet.as_view({ - 'delete': 'destroy' -}) +basket_detail = api.BasketViewSet.as_view({"delete": "destroy"}) -item_count = api.BasketViewSet.as_view({ - 'get': 'item_count' -}) +item_count = api.BasketViewSet.as_view({"get": "item_count"}) -total_items = api.BasketViewSet.as_view({ - 'get': 'total_items' -}) +total_items = api.BasketViewSet.as_view({"get": "total_items"}) urlpatterns = [ - - url(API_URL_PREFIX + r'basket/$', - basket_list, - name='longclaw_basket_list'), - url(API_URL_PREFIX + r'basket/count/$', + path(API_URL_PREFIX + "basket/", basket_list, name="longclaw_basket_list"), + path( + API_URL_PREFIX + "basket/count/", total_items, - name="longclaw_basket_total_items"), - url(API_URL_PREFIX + r'basket/(?P[0-9]+)/$', + name="longclaw_basket_total_items", + ), + path( + API_URL_PREFIX + "basket//", basket_detail, - name='longclaw_basket_detail'), - url(API_URL_PREFIX + r'basket/(?P[0-9]+)/count/$', + name="longclaw_basket_detail", + ), + path( + API_URL_PREFIX + "basket//count/", item_count, - name='longclaw_basket_item_count'), - - url(r'basket/$', - views.BasketView.as_view(), - name="longclaw_basket") + name="longclaw_basket_item_count", + ), + path("basket/", views.BasketView.as_view(), name="longclaw_basket"), ] diff --git a/longclaw/basket/utils.py b/longclaw/basket/utils.py index aedb4fcc..b173a241 100644 --- a/longclaw/basket/utils.py +++ b/longclaw/basket/utils.py @@ -1,21 +1,24 @@ import random + from longclaw.basket.models import BasketItem -BASKET_ID_SESSION_KEY = 'basket_id' +BASKET_ID_SESSION_KEY = "basket_id" + +_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()" -_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890!@#$%^&*()' def basket_id(request): - if not hasattr(request, 'session'): + if not hasattr(request, "session"): request.session = {} - if request.session.get(BASKET_ID_SESSION_KEY, '') == '': + if request.session.get(BASKET_ID_SESSION_KEY, "") == "": request.session[BASKET_ID_SESSION_KEY] = _generate_basket_id() return request.session[BASKET_ID_SESSION_KEY] + def _generate_basket_id(): - basket_id = '' + basket_id = "" for i in range(32): - basket_id += _CHARS[random.randint(0, len(_CHARS)-1)] + basket_id += _CHARS[random.randint(0, len(_CHARS) - 1)] return basket_id @@ -26,9 +29,9 @@ def get_basket_items(request): bid = basket_id(request) return BasketItem.objects.filter(basket_id=bid), bid + def destroy_basket(request): - """Delete all items in the basket - """ + """Delete all items in the basket""" items, bid = get_basket_items(request) for item in items: item.delete() diff --git a/longclaw/basket/views.py b/longclaw/basket/views.py index 0738bb8b..dbe72fe5 100644 --- a/longclaw/basket/views.py +++ b/longclaw/basket/views.py @@ -1,10 +1,13 @@ from django.views.generic import ListView -from longclaw.basket.models import BasketItem + from longclaw.basket import utils +from longclaw.basket.models import BasketItem + class BasketView(ListView): model = BasketItem template_name = "basket/basket.html" + def get_context_data(self, **kwargs): items, _ = utils.get_basket_items(self.request) total_price = sum(item.total() for item in items) diff --git a/longclaw/bin/longclaw.py b/longclaw/bin/longclaw.py index 943d5d93..c9fdc76c 100644 --- a/longclaw/bin/longclaw.py +++ b/longclaw/bin/longclaw.py @@ -1,11 +1,14 @@ -import subprocess import argparse +import os +import subprocess import sys from os import path -import os + from django.core.management import ManagementUtility + import longclaw + def create_project(args): """ Create a new django project using the longclaw template @@ -17,53 +20,61 @@ def create_project(args): except ImportError: pass else: - sys.exit("'{}' conflicts with the name of an existing " - "Python module and cannot be used as a project " - "name. Please try another name.".format(args.project_name)) + sys.exit( + "'{}' conflicts with the name of an existing " + "Python module and cannot be used as a project " + "name. Please try another name.".format(args.project_name) + ) # Get the longclaw template path - template_path = path.join(path.dirname(longclaw.__file__), 'project_template') - - utility = ManagementUtility(( - 'django-admin.py', - 'startproject', - '--template={}'.format(template_path), - '--extension=html,css,js,py,txt', - args.project_name - )) + template_path = path.join(path.dirname(longclaw.__file__), "project_template") + + utility = ManagementUtility( + ( + "django-admin.py", + "startproject", + "--template={}".format(template_path), + "--extension=html,css,js,py,txt", + args.project_name, + ) + ) utility.execute() print("{} has been created.".format(args.project_name)) + def build_assets(args): """ Build the longclaw assets """ # Get the path to the JS directory - asset_path = path.join(path.dirname(longclaw.__file__), 'client') + asset_path = path.join(path.dirname(longclaw.__file__), "client") try: # Move into client dir curdir = os.path.abspath(os.curdir) os.chdir(asset_path) - print('Compiling assets....') - subprocess.check_call(['npm', 'install']) - subprocess.check_call(['npm', 'run', 'build']) + print("Compiling assets....") + subprocess.check_call(["npm", "install"]) + subprocess.check_call(["npm", "run", "build"]) os.chdir(curdir) - print('Complete!') + print("Complete!") except (OSError, subprocess.CalledProcessError) as err: - print('Error compiling assets: {}'.format(err)) + print("Error compiling assets: {}".format(err)) raise SystemExit(1) + def main(): """ Setup the parser and call the command function """ - parser = argparse.ArgumentParser(description='Longclaw CLI') + parser = argparse.ArgumentParser(description="Longclaw CLI") subparsers = parser.add_subparsers() - start = subparsers.add_parser('start', help='Create a Wagtail+Longclaw project') - start.add_argument('project_name', help='Name of the project') + start = subparsers.add_parser("start", help="Create a Wagtail+Longclaw project") + start.add_argument("project_name", help="Name of the project") start.set_defaults(func=create_project) - build = subparsers.add_parser('build', help='Build the front-end assets for Longclaw') + build = subparsers.add_parser( + "build", help="Build the front-end assets for Longclaw" + ) build.set_defaults(func=build_assets) args = parser.parse_args() @@ -79,5 +90,6 @@ def main(): parser.print_help() sys.exit(0) + if __name__ == "__main__": main() diff --git a/longclaw/checkout/__init__.py b/longclaw/checkout/__init__.py index e69de29b..52fdc04e 100644 --- a/longclaw/checkout/__init__.py +++ b/longclaw/checkout/__init__.py @@ -0,0 +1 @@ +# default_app_config = "longclaw.checkout.apps.LongclawCheckoutConfig" diff --git a/longclaw/checkout/api.py b/longclaw/checkout/api.py index 7aaa6e8b..aed632cc 100644 --- a/longclaw/checkout/api.py +++ b/longclaw/checkout/api.py @@ -1,27 +1,29 @@ """ Shipping logic and payment capture API """ -from django.utils import timezone from django.db import transaction -from rest_framework.decorators import api_view, permission_classes +from django.utils import timezone from rest_framework import permissions, status +from rest_framework.decorators import api_view, permission_classes from rest_framework.response import Response + from longclaw.basket.utils import destroy_basket -from longclaw.checkout.utils import create_order, GATEWAY -from longclaw.checkout.errors import PaymentError +from longclaw.checkout.utils import GATEWAY, create_order + -@api_view(['GET']) +@api_view(["GET"]) @permission_classes([permissions.AllowAny]) def create_token(request): - """ Generic function for creating a payment token from the + """Generic function for creating a payment token from the payment backend. Some payment backends (e.g. braintree) support creating a payment token, which should be imported from the backend as 'get_token' """ token = GATEWAY.get_token(request) - return Response({'token': token}, status=status.HTTP_200_OK) + return Response({"token": token}, status=status.HTTP_200_OK) + @transaction.atomic -@api_view(['POST']) +@api_view(["POST"]) @permission_classes([permissions.AllowAny]) def create_order_with_token(request): """ @@ -32,13 +34,15 @@ def create_order_with_token(request): """ # Get the request data try: - address = request.data['address'] - shipping_option = request.data.get('shipping_option', None) - email = request.data['email'] - transaction_id = request.data['transaction_id'] + address = request.data["address"] + shipping_option = request.data.get("shipping_option", None) + email = request.data["email"] + transaction_id = request.data["transaction_id"] except KeyError: - return Response(data={"message": "Missing parameters from request data"}, - status=status.HTTP_400_BAD_REQUEST) + return Response( + data={"message": "Missing parameters from request data"}, + status=status.HTTP_400_BAD_REQUEST, + ) # Create the order order = create_order( @@ -56,8 +60,9 @@ def create_order_with_token(request): return Response(data={"order_id": order.id}, status=status.HTTP_201_CREATED) + @transaction.atomic -@api_view(['POST']) +@api_view(["POST"]) @permission_classes([permissions.AllowAny]) def capture_payment(request): """ @@ -81,9 +86,9 @@ def capture_payment(request): 'shipping': The shipping rate (in the sites' currency) """ # get request data - address = request.data['address'] - email = request.data.get('email', None) - shipping_option = request.data.get('shipping_option', None) + address = request.data["address"] + email = request.data.get("email", None) + shipping_option = request.data.get("shipping_option", None) # Capture the payment order = create_order( @@ -91,9 +96,8 @@ def capture_payment(request): request, addresses=address, shipping_option=shipping_option, - capture_payment=True + capture_payment=True, ) - response = Response(data={"order_id": order.id}, - status=status.HTTP_201_CREATED) + response = Response(data={"order_id": order.id}, status=status.HTTP_201_CREATED) return response diff --git a/longclaw/checkout/apps.py b/longclaw/checkout/apps.py index b53be5fa..7917b0a1 100644 --- a/longclaw/checkout/apps.py +++ b/longclaw/checkout/apps.py @@ -2,4 +2,7 @@ class LongclawCheckoutConfig(AppConfig): - name = 'longclaw.checkout' + label = "longclaw_checkout" + name = "longclaw.checkout" + verbose_name = "Longclaw Checkout" + default_auto_field = "django.db.models.AutoField" diff --git a/longclaw/checkout/errors.py b/longclaw/checkout/errors.py index 274338a5..61fb4419 100644 --- a/longclaw/checkout/errors.py +++ b/longclaw/checkout/errors.py @@ -1,4 +1,3 @@ - class PaymentError(Exception): def __init__(self, message): self.message = str(message) diff --git a/longclaw/checkout/forms.py b/longclaw/checkout/forms.py index def4a521..418cdaab 100644 --- a/longclaw/checkout/forms.py +++ b/longclaw/checkout/forms.py @@ -1,11 +1,14 @@ from django import forms + class CheckoutForm(forms.Form): """ Captures extra info required for checkout """ + email = forms.EmailField() shipping_option = forms.CharField(widget=forms.Select, required=False) different_billing_address = forms.BooleanField(required=False) + class Media: - js = ('checkout.js',) + js = ("checkout.js",) diff --git a/longclaw/checkout/gateways/__init__.py b/longclaw/checkout/gateways/__init__.py index c00f5147..262d35dc 100644 --- a/longclaw/checkout/gateways/__init__.py +++ b/longclaw/checkout/gateways/__init__.py @@ -1,4 +1,4 @@ """ Gateways module to hold payment processor backend logic """ -from longclaw.checkout.gateways.base import BasePayment +# from longclaw.checkout.gateways.base import BasePayment diff --git a/longclaw/checkout/gateways/base.py b/longclaw/checkout/gateways/base.py index 3cb4643f..9221d830 100644 --- a/longclaw/checkout/gateways/base.py +++ b/longclaw/checkout/gateways/base.py @@ -1,12 +1,13 @@ from longclaw.checkout.errors import PaymentError + class BasePayment(object): """ Provides the interface for payment backends and can function as a dummy backend for testing. """ - def create_payment(self, request, amount, description=''): + def create_payment(self, request, amount, description=""): """ Dummy function for creating a payment through a payment gateway. Should be overridden in gateway implementations. @@ -17,7 +18,7 @@ def create_payment(self, request, amount, description=''): if err: raise PaymentError("Dummy error requested") - return 'fake_transaction_id' + return "fake_transaction_id" def get_token(self, request=None): """ @@ -28,7 +29,7 @@ def get_token(self, request=None): This function should be overriden in child classes """ - return 'dummy_token' + return "dummy_token" def client_js(self): """ @@ -37,7 +38,7 @@ def client_js(self): Should return an iterable of JS paths which can be used in - - {% templatetag openblock %} block extra_js {% templatetag closeblock %} - {% templatetag opencomment %} Override this in templates to add extra javascript {% templatetag closecomment %} - {% templatetag openblock %} endblock {% templatetag closeblock %} - - diff --git a/longclaw/project_template/project_name/templates/checkout/success.html b/longclaw/project_template/project_name/templates/checkout/success.html deleted file mode 100644 index ddac4495..00000000 --- a/longclaw/project_template/project_name/templates/checkout/success.html +++ /dev/null @@ -1 +0,0 @@ -{% templatetag openblock %} extends "base.html" {% templatetag closeblock %} diff --git a/longclaw/project_template/project_name/urls.py b/longclaw/project_template/project_name/urls.py deleted file mode 100644 index 3c9256a0..00000000 --- a/longclaw/project_template/project_name/urls.py +++ /dev/null @@ -1,31 +0,0 @@ -from django.conf import settings -from django.conf.urls import include, url -from django.contrib import admin - -from search import views as search_views - -from wagtail.admin import urls as wagtailadmin_urls -from wagtail.core import urls as wagtail_urls -from wagtail.documents import urls as wagtaildocs_urls -from longclaw import urls as longclaw_urls - -urlpatterns = [ - url(r'^django-admin/', admin.site.urls), - - url(r'^admin/', include(wagtailadmin_urls)), - url(r'^documents/', include(wagtaildocs_urls)), - - url(r'^search/$', search_views.search, name='search'), - - url(r'', include(longclaw_urls)), - url(r'', include(wagtail_urls)) -] - - -if settings.DEBUG: - from django.conf.urls.static import static - from django.contrib.staticfiles.urls import staticfiles_urlpatterns - - # Serve static and media files from development server - urlpatterns += staticfiles_urlpatterns() - urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/longclaw/project_template/project_name/wsgi.py b/longclaw/project_template/project_name/wsgi.py deleted file mode 100644 index 74c9ef62..00000000 --- a/longclaw/project_template/project_name/wsgi.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -WSGI config for {{ project_name }} project. - -It exposes the WSGI callable as a module-level variable named ``application``. - -For more information on this file, see -https://docs.djangoproject.com/en/{{ docs_version }}/howto/deployment/wsgi/ -""" -import os - -from django.core.wsgi import get_wsgi_application - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "{{ project_name }}.settings.dev") - -application = get_wsgi_application() diff --git a/longclaw/project_template/requirements.txt b/longclaw/project_template/requirements.txt deleted file mode 100644 index e95771ca..00000000 --- a/longclaw/project_template/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -Django>=2.1.4 -wagtail>=2.4 diff --git a/longclaw/project_template/search/__init__.py b/longclaw/project_template/search/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/longclaw/project_template/search/templates/search/search.html b/longclaw/project_template/search/templates/search/search.html deleted file mode 100644 index ae615ff2..00000000 --- a/longclaw/project_template/search/templates/search/search.html +++ /dev/null @@ -1,38 +0,0 @@ -{% templatetag openblock %} extends "base.html" {% templatetag closeblock %} -{% templatetag openblock %} load static wagtailcore_tags {% templatetag closeblock %} - -{% templatetag openblock %} block body_class {% templatetag closeblock %}template-searchresults{% templatetag openblock %} endblock {% templatetag closeblock %} - -{% templatetag openblock %} block title {% templatetag closeblock %}Search{% templatetag openblock %} endblock {% templatetag closeblock %} - -{% templatetag openblock %} block content {% templatetag closeblock %} -

Search

- -
- - -
- - {% templatetag openblock %} if search_results {% templatetag closeblock %} -
    - {% templatetag openblock %} for result in search_results {% templatetag closeblock %} -
  • -

    {% templatetag openvariable %} result {% templatetag closevariable %}

    - {% templatetag openblock %} if result.search_description {% templatetag closeblock %} - {% templatetag openvariable %} result.search_description|safe {% templatetag closevariable %} - {% templatetag openblock %} endif {% templatetag closeblock %} -
  • - {% templatetag openblock %} endfor {% templatetag closeblock %} -
- - {% templatetag openblock %} if search_results.has_previous {% templatetag closeblock %} - Previous - {% templatetag openblock %} endif {% templatetag closeblock %} - - {% templatetag openblock %} if search_results.has_next {% templatetag closeblock %} - Next - {% templatetag openblock %} endif {% templatetag closeblock %} - {% templatetag openblock %} elif search_query {% templatetag closeblock %} - No results found - {% templatetag openblock %} endif {% templatetag closeblock %} -{% templatetag openblock %} endblock {% templatetag closeblock %} diff --git a/longclaw/project_template/search/views.py b/longclaw/project_template/search/views.py deleted file mode 100644 index 164f1efa..00000000 --- a/longclaw/project_template/search/views.py +++ /dev/null @@ -1,40 +0,0 @@ -from __future__ import absolute_import, unicode_literals - -from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator -from django.shortcuts import render - -try: - from wagtail.core.models import Page - from wagtail.search.models import Query -except ImportError: - from wagtail.wagtailcore.models import Page - from wagtail.wagtailsearch.models import Query - - -def search(request): - search_query = request.GET.get('query', None) - page = request.GET.get('page', 1) - - # Search - if search_query: - search_results = Page.objects.live().search(search_query) - query = Query.get(search_query) - - # Record hit - query.add_hit() - else: - search_results = Page.objects.none() - - # Pagination - paginator = Paginator(search_results, 10) - try: - search_results = paginator.page(page) - except PageNotAnInteger: - search_results = paginator.page(1) - except EmptyPage: - search_results = paginator.page(paginator.num_pages) - - return render(request, 'search/search.html', { - 'search_query': search_query, - 'search_results': search_results, - }) diff --git a/longclaw/settings.py b/longclaw/settings.py index 8bef9775..4de02c71 100644 --- a/longclaw/settings.py +++ b/longclaw/settings.py @@ -8,27 +8,28 @@ # 'longclaw.checkout.gateways.braintree.PaypalVZeroPayment', # 'longclaw.checkout.gateways.stripe.StripePayment' or 'longclaw.checkout.gateways.BasePayment' # Or a custom implementation -PAYMENT_GATEWAY = getattr(settings, - 'PAYMENT_GATEWAY', - 'longclaw.checkout.gateways.base.BasePayment') +PAYMENT_GATEWAY = getattr( + settings, "PAYMENT_GATEWAY", "longclaw.checkout.gateways.base.BasePayment" +) # The product variant model to use. This allows custom implementations of # product models. PRODUCT_VARIANT_MODEL = getattr( - settings, 'PRODUCT_VARIANT_MODEL', 'products.ProductVariantBase') + settings, "PRODUCT_VARIANT_MODEL", "products.ProductVariantBase" +) # Only required if using Stripe as the payment gateway -STRIPE_PUBLISHABLE = getattr(settings, 'STRIPE_PUBLISHABLE', '') -STRIPE_SECRET = getattr(settings, 'STRIPE_SECRET', '') +STRIPE_PUBLISHABLE = getattr(settings, "STRIPE_PUBLISHABLE", "") +STRIPE_SECRET = getattr(settings, "STRIPE_SECRET", "") # Only required if using Braintree as the payment gateway -BRAINTREE_SANDBOX = getattr(settings, 'BRAINTREE_SANDBOX', False) -BRAINTREE_MERCHANT_ID = getattr(settings, 'BRAINTREE_MERCHANT_ID', '') -BRAINTREE_PUBLIC_KEY = getattr(settings, 'BRAINTREE_PUBLIC_KEY', '') -BRAINTREE_PRIVATE_KEY = getattr(settings, 'BRAINTREE_PRIVATE_KEY', '') +BRAINTREE_SANDBOX = getattr(settings, "BRAINTREE_SANDBOX", False) +BRAINTREE_MERCHANT_ID = getattr(settings, "BRAINTREE_MERCHANT_ID", "") +BRAINTREE_PUBLIC_KEY = getattr(settings, "BRAINTREE_PUBLIC_KEY", "") +BRAINTREE_PRIVATE_KEY = getattr(settings, "BRAINTREE_PRIVATE_KEY", "") # Only required for using paypal as the payment gateway through braintree v.zero -VZERO_ACCESS_TOKEN = getattr(settings, 'VZERO_ACCESS_TOKEN', '') +VZERO_ACCESS_TOKEN = getattr(settings, "VZERO_ACCESS_TOKEN", "") -API_URL_PREFIX = getattr(settings, 'LONGCLAW_API_URL_PREFIX', 'api/') +API_URL_PREFIX = getattr(settings, "LONGCLAW_API_URL_PREFIX", "api/") diff --git a/longclaw/shipping/__init__.py b/longclaw/shipping/__init__.py index e69de29b..d3cedb29 100644 --- a/longclaw/shipping/__init__.py +++ b/longclaw/shipping/__init__.py @@ -0,0 +1 @@ +default_apps_config = "longclaw.shipping.apps.LongclawShippingConfig" diff --git a/longclaw/shipping/api.py b/longclaw/shipping/api.py index 6a2a5169..236a52c1 100644 --- a/longclaw/shipping/api.py +++ b/longclaw/shipping/api.py @@ -1,44 +1,49 @@ from django.db.models import Q -from rest_framework.decorators import api_view, permission_classes from rest_framework import permissions, status, viewsets +from rest_framework.decorators import api_view, permission_classes from rest_framework.response import Response -from longclaw.shipping import models, utils, serializers -from longclaw.configuration.models import Configuration + from longclaw.basket.utils import basket_id +from longclaw.configuration.models import Configuration +from longclaw.shipping import models, serializers, utils from .models import ShippingRateProcessor from .signals import address_modified + class AddressViewSet(viewsets.ModelViewSet): """ Create, list and view Addresses """ + queryset = models.Address.objects.all() serializer_class = serializers.AddressSerializer - + def perform_create(self, serializer): - output = super().perform_create(serializer) + super().perform_create(serializer) instance = serializer.instance address_modified.send(sender=models.Address, instance=instance) - + def perform_update(self, serializer): - output = super().perform_update(serializer) + super().perform_update(serializer) instance = serializer.instance address_modified.send(sender=models.Address, instance=instance) - + def perform_destroy(self, instance): - output = super().perform_destroy(instance) + super().perform_destroy(instance) address_modified.send(sender=models.Address, instance=instance) def get_shipping_cost_kwargs(request, country=None): - country_code = request.query_params.get('country_code', None) + country_code = request.query_params.get("country_code", None) if country: if country_code is not None: - raise utils.InvalidShippingCountry("Cannot specify country and country_code") + raise utils.InvalidShippingCountry( + "Cannot specify country and country_code" + ) country_code = country - - destination = request.query_params.get('destination', None) + + destination = request.query_params.get("destination", None) if destination: try: destination = models.Address.objects.get(pk=destination) @@ -46,21 +51,27 @@ def get_shipping_cost_kwargs(request, country=None): raise utils.InvalidShippingDestination("Address not found") elif not country_code: raise utils.InvalidShippingCountry("No country code supplied") - + if not country_code: country_code = destination.country.pk bid = basket_id(request) - option = request.query_params.get('shipping_rate_name', 'standard') + option = request.query_params.get("shipping_rate_name", "standard") settings = Configuration.for_site(request.site) - - return dict(country_code=country_code, destination=destination, basket_id=bid, settings=settings, name=option) + + return dict( + country_code=country_code, + destination=destination, + basket_id=bid, + settings=settings, + name=option, + ) -@api_view(['GET']) +@api_view(["GET"]) @permission_classes({permissions.AllowAny}) def shipping_cost(request): - """ Returns the shipping cost for a given country + """Returns the shipping cost for a given country If the shipping cost for the given country has not been set, it will fallback to the default shipping cost if it has been enabled in the app settings @@ -69,17 +80,17 @@ def shipping_cost(request): try: kwargs = get_shipping_cost_kwargs(request) except (utils.InvalidShippingCountry, utils.InvalidShippingDestination) as e: - data = {'message': e.message} + data = {"message": e.message} else: try: data = utils.get_shipping_cost(**kwargs) except utils.InvalidShippingRate: - data = { - "message": "Shipping option {} is invalid".format(kwargs['name']) - } + data = {"message": "Shipping option {} is invalid".format(kwargs["name"])} except utils.InvalidShippingCountry: data = { - "message": "Shipping to {} is not available".format(kwargs['country_code']) + "message": "Shipping to {} is not available".format( + kwargs["country_code"] + ) } else: status_code = status.HTTP_200_OK @@ -90,12 +101,12 @@ def shipping_cost(request): @api_view(["GET"]) @permission_classes([permissions.AllowAny]) def shipping_countries(request): - """ Get all shipping countries - """ + """Get all shipping countries""" queryset = models.Country.objects.exclude(shippingrate=None) serializer = serializers.CountrySerializer(queryset, many=True) return Response(data=serializer.data, status=status.HTTP_200_OK) + @api_view(["GET"]) @permission_classes([permissions.AllowAny]) def shipping_options(request, country=None): @@ -105,34 +116,35 @@ def shipping_options(request, country=None): try: kwargs = get_shipping_cost_kwargs(request, country=country) except (utils.InvalidShippingCountry, utils.InvalidShippingDestination) as e: - return Response(data={'message': e.message}, status=status.HTTP_400_BAD_REQUEST) - - country_code = kwargs['country_code'] - settings = kwargs['settings'] - bid = kwargs['basket_id'] - destination = kwargs['destination'] - + return Response(data={"message": e.message}, status=status.HTTP_400_BAD_REQUEST) + + country_code = kwargs["country_code"] + settings = kwargs["settings"] + bid = kwargs["basket_id"] + destination = kwargs["destination"] + processors = ShippingRateProcessor.objects.filter(countries__in=[country_code]) if processors: if not destination: return Response( data={ - "message": "Destination address is required for rates to {}.".format(country_code) + "message": "Destination address is required for rates to {}.".format( + country_code + ) }, - status=status.HTTP_400_BAD_REQUEST + status=status.HTTP_400_BAD_REQUEST, ) for processor in processors: - processor.get_rates(settings=settings, basket_id=bid, destination=destination) - + processor.get_rates( + settings=settings, basket_id=bid, destination=destination + ) + q = Q(countries__in=[country_code]) | Q(basket_id=bid, destination=None) - + if destination: - q.add(Q(destination=destination, basket_id=''), Q.OR) + q.add(Q(destination=destination, basket_id=""), Q.OR) q.add(Q(destination=destination, basket_id=bid), Q.OR) - + qrs = models.ShippingRate.objects.filter(q) serializer = serializers.ShippingRateSerializer(qrs, many=True) - return Response( - data=serializer.data, - status=status.HTTP_200_OK - ) + return Response(data=serializer.data, status=status.HTTP_200_OK) diff --git a/longclaw/shipping/apps.py b/longclaw/shipping/apps.py index a1172aa0..6fe7c66a 100644 --- a/longclaw/shipping/apps.py +++ b/longclaw/shipping/apps.py @@ -2,4 +2,7 @@ class LongclawShippingConfig(AppConfig): - name = 'longclaw.shipping' + label = "longclaw_shipping" + name = "longclaw.shipping" + verbose_name = "Longclaw Shipping" + default_auto_field = "django.db.models.AutoField" diff --git a/longclaw/shipping/fixtures/shipping_initial.json b/longclaw/shipping/fixtures/shipping_initial.json index aae346ea..c3296533 100644 --- a/longclaw/shipping/fixtures/shipping_initial.json +++ b/longclaw/shipping/fixtures/shipping_initial.json @@ -1,2216 +1,2216 @@ [ { - "pk": "AF", - "model": "shipping.country", + "pk": "AF", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "AFGHANISTAN", + "sort_priority": 0, + "name_official": "AFGHANISTAN", "name": "Afghanistan" } - }, + }, { - "pk": "AX", - "model": "shipping.country", + "pk": "AX", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "\u00c5LAND ISLANDS", + "sort_priority": 0, + "name_official": "\u00c5LAND ISLANDS", "name": "\u00c5Land Islands" } - }, + }, { - "pk": "AL", - "model": "shipping.country", + "pk": "AL", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ALBANIA", + "sort_priority": 0, + "name_official": "ALBANIA", "name": "Albania" } - }, + }, { - "pk": "DZ", - "model": "shipping.country", + "pk": "DZ", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ALGERIA", + "sort_priority": 0, + "name_official": "ALGERIA", "name": "Algeria" } - }, + }, { - "pk": "AS", - "model": "shipping.country", + "pk": "AS", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "AMERICAN SAMOA", + "sort_priority": 0, + "name_official": "AMERICAN SAMOA", "name": "American Samoa" } - }, + }, { - "pk": "AD", - "model": "shipping.country", + "pk": "AD", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ANDORRA", + "sort_priority": 0, + "name_official": "ANDORRA", "name": "Andorra" } - }, + }, { - "pk": "AO", - "model": "shipping.country", + "pk": "AO", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ANGOLA", + "sort_priority": 0, + "name_official": "ANGOLA", "name": "Angola" } - }, + }, { - "pk": "AI", - "model": "shipping.country", + "pk": "AI", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ANGUILLA", + "sort_priority": 0, + "name_official": "ANGUILLA", "name": "Anguilla" } - }, + }, { - "pk": "AQ", - "model": "shipping.country", + "pk": "AQ", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ANTARCTICA", + "sort_priority": 0, + "name_official": "ANTARCTICA", "name": "Antarctica" } - }, + }, { - "pk": "AG", - "model": "shipping.country", + "pk": "AG", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ANTIGUA AND BARBUDA", + "sort_priority": 0, + "name_official": "ANTIGUA AND BARBUDA", "name": "Antigua And Barbuda" } - }, + }, { - "pk": "AR", - "model": "shipping.country", + "pk": "AR", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ARGENTINA", + "sort_priority": 0, + "name_official": "ARGENTINA", "name": "Argentina" } - }, + }, { - "pk": "AM", - "model": "shipping.country", + "pk": "AM", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ARMENIA", + "sort_priority": 0, + "name_official": "ARMENIA", "name": "Armenia" } - }, + }, { - "pk": "AW", - "model": "shipping.country", + "pk": "AW", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ARUBA", + "sort_priority": 0, + "name_official": "ARUBA", "name": "Aruba" } - }, + }, { - "pk": "AU", - "model": "shipping.country", + "pk": "AU", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "AUSTRALIA", + "sort_priority": 0, + "name_official": "AUSTRALIA", "name": "Australia" } - }, + }, { - "pk": "AT", - "model": "shipping.country", + "pk": "AT", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "AUSTRIA", + "sort_priority": 0, + "name_official": "AUSTRIA", "name": "Austria" } - }, + }, { - "pk": "AZ", - "model": "shipping.country", + "pk": "AZ", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "AZERBAIJAN", + "sort_priority": 0, + "name_official": "AZERBAIJAN", "name": "Azerbaijan" } - }, + }, { - "pk": "BS", - "model": "shipping.country", + "pk": "BS", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "BAHAMAS", + "sort_priority": 0, + "name_official": "BAHAMAS", "name": "Bahamas" } - }, + }, { - "pk": "BH", - "model": "shipping.country", + "pk": "BH", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "BAHRAIN", + "sort_priority": 0, + "name_official": "BAHRAIN", "name": "Bahrain" } - }, + }, { - "pk": "BD", - "model": "shipping.country", + "pk": "BD", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "BANGLADESH", + "sort_priority": 0, + "name_official": "BANGLADESH", "name": "Bangladesh" } - }, + }, { - "pk": "BB", - "model": "shipping.country", + "pk": "BB", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "BARBADOS", + "sort_priority": 0, + "name_official": "BARBADOS", "name": "Barbados" } - }, + }, { - "pk": "BY", - "model": "shipping.country", + "pk": "BY", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "BELARUS", + "sort_priority": 0, + "name_official": "BELARUS", "name": "Belarus" } - }, + }, { - "pk": "BE", - "model": "shipping.country", + "pk": "BE", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "BELGIUM", + "sort_priority": 0, + "name_official": "BELGIUM", "name": "Belgium" } - }, + }, { - "pk": "BZ", - "model": "shipping.country", + "pk": "BZ", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "BELIZE", + "sort_priority": 0, + "name_official": "BELIZE", "name": "Belize" } - }, + }, { - "pk": "BJ", - "model": "shipping.country", + "pk": "BJ", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "BENIN", + "sort_priority": 0, + "name_official": "BENIN", "name": "Benin" } - }, + }, { - "pk": "BM", - "model": "shipping.country", + "pk": "BM", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "BERMUDA", + "sort_priority": 0, + "name_official": "BERMUDA", "name": "Bermuda" } - }, + }, { - "pk": "BT", - "model": "shipping.country", + "pk": "BT", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "BHUTAN", + "sort_priority": 0, + "name_official": "BHUTAN", "name": "Bhutan" } - }, + }, { - "pk": "BO", - "model": "shipping.country", + "pk": "BO", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "BOLIVIA, PLURINATIONAL STATE OF", + "sort_priority": 0, + "name_official": "BOLIVIA, PLURINATIONAL STATE OF", "name": "Bolivia, Plurinational State Of" } - }, + }, { - "pk": "BA", - "model": "shipping.country", + "pk": "BA", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "BOSNIA AND HERZEGOVINA", + "sort_priority": 0, + "name_official": "BOSNIA AND HERZEGOVINA", "name": "Bosnia And Herzegovina" } - }, + }, { - "pk": "BW", - "model": "shipping.country", + "pk": "BW", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "BOTSWANA", + "sort_priority": 0, + "name_official": "BOTSWANA", "name": "Botswana" } - }, + }, { - "pk": "BV", - "model": "shipping.country", + "pk": "BV", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "BOUVET ISLAND", + "sort_priority": 0, + "name_official": "BOUVET ISLAND", "name": "Bouvet Island" } - }, + }, { - "pk": "BR", - "model": "shipping.country", + "pk": "BR", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "BRAZIL", + "sort_priority": 0, + "name_official": "BRAZIL", "name": "Brazil" } - }, + }, { - "pk": "IO", - "model": "shipping.country", + "pk": "IO", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "BRITISH INDIAN OCEAN TERRITORY", + "sort_priority": 0, + "name_official": "BRITISH INDIAN OCEAN TERRITORY", "name": "British Indian Ocean Territory" } - }, + }, { - "pk": "BN", - "model": "shipping.country", + "pk": "BN", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "BRUNEI DARUSSALAM", + "sort_priority": 0, + "name_official": "BRUNEI DARUSSALAM", "name": "Brunei Darussalam" } - }, + }, { - "pk": "BG", - "model": "shipping.country", + "pk": "BG", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "BULGARIA", + "sort_priority": 0, + "name_official": "BULGARIA", "name": "Bulgaria" } - }, + }, { - "pk": "BF", - "model": "shipping.country", + "pk": "BF", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "BURKINA FASO", + "sort_priority": 0, + "name_official": "BURKINA FASO", "name": "Burkina Faso" } - }, + }, { - "pk": "BI", - "model": "shipping.country", + "pk": "BI", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "BURUNDI", + "sort_priority": 0, + "name_official": "BURUNDI", "name": "Burundi" } - }, + }, { - "pk": "KH", - "model": "shipping.country", + "pk": "KH", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "CAMBODIA", + "sort_priority": 0, + "name_official": "CAMBODIA", "name": "Cambodia" } - }, + }, { - "pk": "CM", - "model": "shipping.country", + "pk": "CM", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "CAMEROON", + "sort_priority": 0, + "name_official": "CAMEROON", "name": "Cameroon" } - }, + }, { - "pk": "CA", - "model": "shipping.country", + "pk": "CA", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "CANADA", + "sort_priority": 0, + "name_official": "CANADA", "name": "Canada" } - }, + }, { - "pk": "CV", - "model": "shipping.country", + "pk": "CV", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "CAPE VERDE", + "sort_priority": 0, + "name_official": "CAPE VERDE", "name": "Cape Verde" } - }, + }, { - "pk": "KY", - "model": "shipping.country", + "pk": "KY", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "CAYMAN ISLANDS", + "sort_priority": 0, + "name_official": "CAYMAN ISLANDS", "name": "Cayman Islands" } - }, + }, { - "pk": "CF", - "model": "shipping.country", + "pk": "CF", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "CENTRAL AFRICAN REPUBLIC", + "sort_priority": 0, + "name_official": "CENTRAL AFRICAN REPUBLIC", "name": "Central African Republic" } - }, + }, { - "pk": "TD", - "model": "shipping.country", + "pk": "TD", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "CHAD", + "sort_priority": 0, + "name_official": "CHAD", "name": "Chad" } - }, + }, { - "pk": "CL", - "model": "shipping.country", + "pk": "CL", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "CHILE", + "sort_priority": 0, + "name_official": "CHILE", "name": "Chile" } - }, + }, { - "pk": "CN", - "model": "shipping.country", + "pk": "CN", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "CHINA", + "sort_priority": 0, + "name_official": "CHINA", "name": "China" } - }, + }, { - "pk": "CX", - "model": "shipping.country", + "pk": "CX", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "CHRISTMAS ISLAND", + "sort_priority": 0, + "name_official": "CHRISTMAS ISLAND", "name": "Christmas Island" } - }, + }, { - "pk": "CC", - "model": "shipping.country", + "pk": "CC", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "COCOS (KEELING) ISLANDS", + "sort_priority": 0, + "name_official": "COCOS (KEELING) ISLANDS", "name": "Cocos (Keeling) Islands" } - }, + }, { - "pk": "CO", - "model": "shipping.country", + "pk": "CO", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "COLOMBIA", + "sort_priority": 0, + "name_official": "COLOMBIA", "name": "Colombia" } - }, + }, { - "pk": "KM", - "model": "shipping.country", + "pk": "KM", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "COMOROS", + "sort_priority": 0, + "name_official": "COMOROS", "name": "Comoros" } - }, + }, { - "pk": "CG", - "model": "shipping.country", + "pk": "CG", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "CONGO", + "sort_priority": 0, + "name_official": "CONGO", "name": "Congo" } - }, + }, { - "pk": "CD", - "model": "shipping.country", + "pk": "CD", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "CONGO, THE DEMOCRATIC REPUBLIC OF THE", + "sort_priority": 0, + "name_official": "CONGO, THE DEMOCRATIC REPUBLIC OF THE", "name": "Congo, The Democratic Republic Of The" } - }, + }, { - "pk": "CK", - "model": "shipping.country", + "pk": "CK", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "COOK ISLANDS", + "sort_priority": 0, + "name_official": "COOK ISLANDS", "name": "Cook Islands" } - }, + }, { - "pk": "CR", - "model": "shipping.country", + "pk": "CR", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "COSTA RICA", + "sort_priority": 0, + "name_official": "COSTA RICA", "name": "Costa Rica" } - }, + }, { - "pk": "CI", - "model": "shipping.country", + "pk": "CI", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "C\u00d4TE D'IVOIRE", + "sort_priority": 0, + "name_official": "C\u00d4TE D'IVOIRE", "name": "C\u00d4Te D'Ivoire" } - }, + }, { - "pk": "HR", - "model": "shipping.country", + "pk": "HR", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "CROATIA", + "sort_priority": 0, + "name_official": "CROATIA", "name": "Croatia" } - }, + }, { - "pk": "CU", - "model": "shipping.country", + "pk": "CU", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "CUBA", + "sort_priority": 0, + "name_official": "CUBA", "name": "Cuba" } - }, + }, { - "pk": "CY", - "model": "shipping.country", + "pk": "CY", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "CYPRUS", + "sort_priority": 0, + "name_official": "CYPRUS", "name": "Cyprus" } - }, + }, { - "pk": "CZ", - "model": "shipping.country", + "pk": "CZ", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "CZECH REPUBLIC", + "sort_priority": 0, + "name_official": "CZECH REPUBLIC", "name": "Czech Republic" } - }, + }, { - "pk": "DK", - "model": "shipping.country", + "pk": "DK", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "DENMARK", + "sort_priority": 0, + "name_official": "DENMARK", "name": "Denmark" } - }, + }, { - "pk": "DJ", - "model": "shipping.country", + "pk": "DJ", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "DJIBOUTI", + "sort_priority": 0, + "name_official": "DJIBOUTI", "name": "Djibouti" } - }, + }, { - "pk": "DM", - "model": "shipping.country", + "pk": "DM", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "DOMINICA", + "sort_priority": 0, + "name_official": "DOMINICA", "name": "Dominica" } - }, + }, { - "pk": "DO", - "model": "shipping.country", + "pk": "DO", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "DOMINICAN REPUBLIC", + "sort_priority": 0, + "name_official": "DOMINICAN REPUBLIC", "name": "Dominican Republic" } - }, + }, { - "pk": "EC", - "model": "shipping.country", + "pk": "EC", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ECUADOR", + "sort_priority": 0, + "name_official": "ECUADOR", "name": "Ecuador" } - }, + }, { - "pk": "EG", - "model": "shipping.country", + "pk": "EG", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "EGYPT", + "sort_priority": 0, + "name_official": "EGYPT", "name": "Egypt" } - }, + }, { - "pk": "SV", - "model": "shipping.country", + "pk": "SV", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "EL SALVADOR", + "sort_priority": 0, + "name_official": "EL SALVADOR", "name": "El Salvador" } - }, + }, { - "pk": "GQ", - "model": "shipping.country", + "pk": "GQ", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "EQUATORIAL GUINEA", + "sort_priority": 0, + "name_official": "EQUATORIAL GUINEA", "name": "Equatorial Guinea" } - }, + }, { - "pk": "ER", - "model": "shipping.country", + "pk": "ER", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ERITREA", + "sort_priority": 0, + "name_official": "ERITREA", "name": "Eritrea" } - }, + }, { - "pk": "EE", - "model": "shipping.country", + "pk": "EE", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ESTONIA", + "sort_priority": 0, + "name_official": "ESTONIA", "name": "Estonia" } - }, + }, { - "pk": "ET", - "model": "shipping.country", + "pk": "ET", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ETHIOPIA", + "sort_priority": 0, + "name_official": "ETHIOPIA", "name": "Ethiopia" } - }, + }, { - "pk": "FK", - "model": "shipping.country", + "pk": "FK", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "FALKLAND ISLANDS (MALVINAS)", + "sort_priority": 0, + "name_official": "FALKLAND ISLANDS (MALVINAS)", "name": "Falkland Islands (Malvinas)" } - }, + }, { - "pk": "FO", - "model": "shipping.country", + "pk": "FO", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "FAROE ISLANDS", + "sort_priority": 0, + "name_official": "FAROE ISLANDS", "name": "Faroe Islands" } - }, + }, { - "pk": "FJ", - "model": "shipping.country", + "pk": "FJ", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "FIJI", + "sort_priority": 0, + "name_official": "FIJI", "name": "Fiji" } - }, + }, { - "pk": "FI", - "model": "shipping.country", + "pk": "FI", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "FINLAND", + "sort_priority": 0, + "name_official": "FINLAND", "name": "Finland" } - }, + }, { - "pk": "FR", - "model": "shipping.country", + "pk": "FR", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "FRANCE", + "sort_priority": 0, + "name_official": "FRANCE", "name": "France" } - }, + }, { - "pk": "GF", - "model": "shipping.country", + "pk": "GF", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "FRENCH GUIANA", + "sort_priority": 0, + "name_official": "FRENCH GUIANA", "name": "French Guiana" } - }, + }, { - "pk": "PF", - "model": "shipping.country", + "pk": "PF", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "FRENCH POLYNESIA", + "sort_priority": 0, + "name_official": "FRENCH POLYNESIA", "name": "French Polynesia" } - }, + }, { - "pk": "TF", - "model": "shipping.country", + "pk": "TF", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "FRENCH SOUTHERN TERRITORIES", + "sort_priority": 0, + "name_official": "FRENCH SOUTHERN TERRITORIES", "name": "French Southern Territories" } - }, + }, { - "pk": "GA", - "model": "shipping.country", + "pk": "GA", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "GABON", + "sort_priority": 0, + "name_official": "GABON", "name": "Gabon" } - }, + }, { - "pk": "GM", - "model": "shipping.country", + "pk": "GM", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "GAMBIA", + "sort_priority": 0, + "name_official": "GAMBIA", "name": "Gambia" } - }, + }, { - "pk": "GE", - "model": "shipping.country", + "pk": "GE", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "GEORGIA", + "sort_priority": 0, + "name_official": "GEORGIA", "name": "Georgia" } - }, + }, { - "pk": "DE", - "model": "shipping.country", + "pk": "DE", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "GERMANY", + "sort_priority": 0, + "name_official": "GERMANY", "name": "Germany" } - }, + }, { - "pk": "GH", - "model": "shipping.country", + "pk": "GH", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "GHANA", + "sort_priority": 0, + "name_official": "GHANA", "name": "Ghana" } - }, + }, { - "pk": "GI", - "model": "shipping.country", + "pk": "GI", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "GIBRALTAR", + "sort_priority": 0, + "name_official": "GIBRALTAR", "name": "Gibraltar" } - }, + }, { - "pk": "GR", - "model": "shipping.country", + "pk": "GR", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "GREECE", + "sort_priority": 0, + "name_official": "GREECE", "name": "Greece" } - }, + }, { - "pk": "GL", - "model": "shipping.country", + "pk": "GL", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "GREENLAND", + "sort_priority": 0, + "name_official": "GREENLAND", "name": "Greenland" } - }, + }, { - "pk": "GD", - "model": "shipping.country", + "pk": "GD", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "GRENADA", + "sort_priority": 0, + "name_official": "GRENADA", "name": "Grenada" } - }, + }, { - "pk": "GP", - "model": "shipping.country", + "pk": "GP", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "GUADELOUPE", + "sort_priority": 0, + "name_official": "GUADELOUPE", "name": "Guadeloupe" } - }, + }, { - "pk": "GU", - "model": "shipping.country", + "pk": "GU", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "GUAM", + "sort_priority": 0, + "name_official": "GUAM", "name": "Guam" } - }, + }, { - "pk": "GT", - "model": "shipping.country", + "pk": "GT", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "GUATEMALA", + "sort_priority": 0, + "name_official": "GUATEMALA", "name": "Guatemala" } - }, + }, { - "pk": "GG", - "model": "shipping.country", + "pk": "GG", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "GUERNSEY", + "sort_priority": 0, + "name_official": "GUERNSEY", "name": "Guernsey" } - }, + }, { - "pk": "GN", - "model": "shipping.country", + "pk": "GN", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "GUINEA", + "sort_priority": 0, + "name_official": "GUINEA", "name": "Guinea" } - }, + }, { - "pk": "GW", - "model": "shipping.country", + "pk": "GW", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "GUINEA-BISSAU", + "sort_priority": 0, + "name_official": "GUINEA-BISSAU", "name": "Guinea-Bissau" } - }, + }, { - "pk": "GY", - "model": "shipping.country", + "pk": "GY", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "GUYANA", + "sort_priority": 0, + "name_official": "GUYANA", "name": "Guyana" } - }, + }, { - "pk": "HT", - "model": "shipping.country", + "pk": "HT", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "HAITI", + "sort_priority": 0, + "name_official": "HAITI", "name": "Haiti" } - }, + }, { - "pk": "HM", - "model": "shipping.country", + "pk": "HM", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "HEARD ISLAND AND MCDONALD ISLANDS", + "sort_priority": 0, + "name_official": "HEARD ISLAND AND MCDONALD ISLANDS", "name": "Heard Island And Mcdonald Islands" } - }, + }, { - "pk": "VA", - "model": "shipping.country", + "pk": "VA", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "HOLY SEE (VATICAN CITY STATE)", + "sort_priority": 0, + "name_official": "HOLY SEE (VATICAN CITY STATE)", "name": "Holy See (Vatican City State)" } - }, + }, { - "pk": "HN", - "model": "shipping.country", + "pk": "HN", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "HONDURAS", + "sort_priority": 0, + "name_official": "HONDURAS", "name": "Honduras" } - }, + }, { - "pk": "HK", - "model": "shipping.country", + "pk": "HK", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "HONG KONG", + "sort_priority": 0, + "name_official": "HONG KONG", "name": "Hong Kong" } - }, + }, { - "pk": "HU", - "model": "shipping.country", + "pk": "HU", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "HUNGARY", + "sort_priority": 0, + "name_official": "HUNGARY", "name": "Hungary" } - }, + }, { - "pk": "IS", - "model": "shipping.country", + "pk": "IS", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ICELAND", + "sort_priority": 0, + "name_official": "ICELAND", "name": "Iceland" } - }, + }, { - "pk": "IN", - "model": "shipping.country", + "pk": "IN", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "INDIA", + "sort_priority": 0, + "name_official": "INDIA", "name": "India" } - }, + }, { - "pk": "ID", - "model": "shipping.country", + "pk": "ID", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "INDONESIA", + "sort_priority": 0, + "name_official": "INDONESIA", "name": "Indonesia" } - }, + }, { - "pk": "IR", - "model": "shipping.country", + "pk": "IR", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "IRAN, ISLAMIC REPUBLIC OF", + "sort_priority": 0, + "name_official": "IRAN, ISLAMIC REPUBLIC OF", "name": "Iran, Islamic Republic Of" } - }, + }, { - "pk": "IQ", - "model": "shipping.country", + "pk": "IQ", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "IRAQ", + "sort_priority": 0, + "name_official": "IRAQ", "name": "Iraq" } - }, + }, { - "pk": "IE", - "model": "shipping.country", + "pk": "IE", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "IRELAND", + "sort_priority": 0, + "name_official": "IRELAND", "name": "Ireland" } - }, + }, { - "pk": "IM", - "model": "shipping.country", + "pk": "IM", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ISLE OF MAN", + "sort_priority": 0, + "name_official": "ISLE OF MAN", "name": "Isle Of Man" } - }, + }, { - "pk": "IL", - "model": "shipping.country", + "pk": "IL", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ISRAEL", + "sort_priority": 0, + "name_official": "ISRAEL", "name": "Israel" } - }, + }, { - "pk": "IT", - "model": "shipping.country", + "pk": "IT", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ITALY", + "sort_priority": 0, + "name_official": "ITALY", "name": "Italy" } - }, + }, { - "pk": "JM", - "model": "shipping.country", + "pk": "JM", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "JAMAICA", + "sort_priority": 0, + "name_official": "JAMAICA", "name": "Jamaica" } - }, + }, { - "pk": "JP", - "model": "shipping.country", + "pk": "JP", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "JAPAN", + "sort_priority": 0, + "name_official": "JAPAN", "name": "Japan" } - }, + }, { - "pk": "JE", - "model": "shipping.country", + "pk": "JE", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "JERSEY", + "sort_priority": 0, + "name_official": "JERSEY", "name": "Jersey" } - }, + }, { - "pk": "JO", - "model": "shipping.country", + "pk": "JO", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "JORDAN", + "sort_priority": 0, + "name_official": "JORDAN", "name": "Jordan" } - }, + }, { - "pk": "KZ", - "model": "shipping.country", + "pk": "KZ", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "KAZAKHSTAN", + "sort_priority": 0, + "name_official": "KAZAKHSTAN", "name": "Kazakhstan" } - }, + }, { - "pk": "KE", - "model": "shipping.country", + "pk": "KE", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "KENYA", + "sort_priority": 0, + "name_official": "KENYA", "name": "Kenya" } - }, + }, { - "pk": "KI", - "model": "shipping.country", + "pk": "KI", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "KIRIBATI", + "sort_priority": 0, + "name_official": "KIRIBATI", "name": "Kiribati" } - }, + }, { - "pk": "KP", - "model": "shipping.country", + "pk": "KP", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF", + "sort_priority": 0, + "name_official": "KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF", "name": "Korea, Democratic People'S Republic Of" } - }, + }, { - "pk": "KR", - "model": "shipping.country", + "pk": "KR", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "KOREA, REPUBLIC OF", + "sort_priority": 0, + "name_official": "KOREA, REPUBLIC OF", "name": "Korea, Republic Of" } - }, + }, { - "pk": "KW", - "model": "shipping.country", + "pk": "KW", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "KUWAIT", + "sort_priority": 0, + "name_official": "KUWAIT", "name": "Kuwait" } - }, + }, { - "pk": "KG", - "model": "shipping.country", + "pk": "KG", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "KYRGYZSTAN", + "sort_priority": 0, + "name_official": "KYRGYZSTAN", "name": "Kyrgyzstan" } - }, + }, { - "pk": "LA", - "model": "shipping.country", + "pk": "LA", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "LAO PEOPLE'S DEMOCRATIC REPUBLIC", + "sort_priority": 0, + "name_official": "LAO PEOPLE'S DEMOCRATIC REPUBLIC", "name": "Lao People'S Democratic Republic" } - }, + }, { - "pk": "LV", - "model": "shipping.country", + "pk": "LV", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "LATVIA", + "sort_priority": 0, + "name_official": "LATVIA", "name": "Latvia" } - }, + }, { - "pk": "LB", - "model": "shipping.country", + "pk": "LB", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "LEBANON", + "sort_priority": 0, + "name_official": "LEBANON", "name": "Lebanon" } - }, + }, { - "pk": "LS", - "model": "shipping.country", + "pk": "LS", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "LESOTHO", + "sort_priority": 0, + "name_official": "LESOTHO", "name": "Lesotho" } - }, + }, { - "pk": "LR", - "model": "shipping.country", + "pk": "LR", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "LIBERIA", + "sort_priority": 0, + "name_official": "LIBERIA", "name": "Liberia" } - }, + }, { - "pk": "LY", - "model": "shipping.country", + "pk": "LY", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "LIBYAN ARAB JAMAHIRIYA", + "sort_priority": 0, + "name_official": "LIBYAN ARAB JAMAHIRIYA", "name": "Libyan Arab Jamahiriya" } - }, + }, { - "pk": "LI", - "model": "shipping.country", + "pk": "LI", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "LIECHTENSTEIN", + "sort_priority": 0, + "name_official": "LIECHTENSTEIN", "name": "Liechtenstein" } - }, + }, { - "pk": "LT", - "model": "shipping.country", + "pk": "LT", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "LITHUANIA", + "sort_priority": 0, + "name_official": "LITHUANIA", "name": "Lithuania" } - }, + }, { - "pk": "LU", - "model": "shipping.country", + "pk": "LU", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "LUXEMBOURG", + "sort_priority": 0, + "name_official": "LUXEMBOURG", "name": "Luxembourg" } - }, + }, { - "pk": "MO", - "model": "shipping.country", + "pk": "MO", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MACAO", + "sort_priority": 0, + "name_official": "MACAO", "name": "Macao" } - }, + }, { - "pk": "MK", - "model": "shipping.country", + "pk": "MK", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF", + "sort_priority": 0, + "name_official": "MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF", "name": "Macedonia, The Former Yugoslav Republic Of" } - }, + }, { - "pk": "MG", - "model": "shipping.country", + "pk": "MG", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MADAGASCAR", + "sort_priority": 0, + "name_official": "MADAGASCAR", "name": "Madagascar" } - }, + }, { - "pk": "MW", - "model": "shipping.country", + "pk": "MW", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MALAWI", + "sort_priority": 0, + "name_official": "MALAWI", "name": "Malawi" } - }, + }, { - "pk": "MY", - "model": "shipping.country", + "pk": "MY", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MALAYSIA", + "sort_priority": 0, + "name_official": "MALAYSIA", "name": "Malaysia" } - }, + }, { - "pk": "MV", - "model": "shipping.country", + "pk": "MV", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MALDIVES", + "sort_priority": 0, + "name_official": "MALDIVES", "name": "Maldives" } - }, + }, { - "pk": "ML", - "model": "shipping.country", + "pk": "ML", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MALI", + "sort_priority": 0, + "name_official": "MALI", "name": "Mali" } - }, + }, { - "pk": "MT", - "model": "shipping.country", + "pk": "MT", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MALTA", + "sort_priority": 0, + "name_official": "MALTA", "name": "Malta" } - }, + }, { - "pk": "MH", - "model": "shipping.country", + "pk": "MH", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MARSHALL ISLANDS", + "sort_priority": 0, + "name_official": "MARSHALL ISLANDS", "name": "Marshall Islands" } - }, + }, { - "pk": "MQ", - "model": "shipping.country", + "pk": "MQ", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MARTINIQUE", + "sort_priority": 0, + "name_official": "MARTINIQUE", "name": "Martinique" } - }, + }, { - "pk": "MR", - "model": "shipping.country", + "pk": "MR", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MAURITANIA", + "sort_priority": 0, + "name_official": "MAURITANIA", "name": "Mauritania" } - }, + }, { - "pk": "MU", - "model": "shipping.country", + "pk": "MU", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MAURITIUS", + "sort_priority": 0, + "name_official": "MAURITIUS", "name": "Mauritius" } - }, + }, { - "pk": "YT", - "model": "shipping.country", + "pk": "YT", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MAYOTTE", + "sort_priority": 0, + "name_official": "MAYOTTE", "name": "Mayotte" } - }, + }, { - "pk": "MX", - "model": "shipping.country", + "pk": "MX", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MEXICO", + "sort_priority": 0, + "name_official": "MEXICO", "name": "Mexico" } - }, + }, { - "pk": "FM", - "model": "shipping.country", + "pk": "FM", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MICRONESIA, FEDERATED STATES OF", + "sort_priority": 0, + "name_official": "MICRONESIA, FEDERATED STATES OF", "name": "Micronesia, Federated States Of" } - }, + }, { - "pk": "MD", - "model": "shipping.country", + "pk": "MD", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MOLDOVA, REPUBLIC OF", + "sort_priority": 0, + "name_official": "MOLDOVA, REPUBLIC OF", "name": "Moldova, Republic Of" } - }, + }, { - "pk": "MC", - "model": "shipping.country", + "pk": "MC", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MONACO", + "sort_priority": 0, + "name_official": "MONACO", "name": "Monaco" } - }, + }, { - "pk": "MN", - "model": "shipping.country", + "pk": "MN", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MONGOLIA", + "sort_priority": 0, + "name_official": "MONGOLIA", "name": "Mongolia" } - }, + }, { - "pk": "ME", - "model": "shipping.country", + "pk": "ME", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MONTENEGRO", + "sort_priority": 0, + "name_official": "MONTENEGRO", "name": "Montenegro" } - }, + }, { - "pk": "MS", - "model": "shipping.country", + "pk": "MS", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MONTSERRAT", + "sort_priority": 0, + "name_official": "MONTSERRAT", "name": "Montserrat" } - }, + }, { - "pk": "MA", - "model": "shipping.country", + "pk": "MA", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MOROCCO", + "sort_priority": 0, + "name_official": "MOROCCO", "name": "Morocco" } - }, + }, { - "pk": "MZ", - "model": "shipping.country", + "pk": "MZ", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MOZAMBIQUE", + "sort_priority": 0, + "name_official": "MOZAMBIQUE", "name": "Mozambique" } - }, + }, { - "pk": "MM", - "model": "shipping.country", + "pk": "MM", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "MYANMAR", + "sort_priority": 0, + "name_official": "MYANMAR", "name": "Myanmar" } - }, + }, { - "pk": "NA", - "model": "shipping.country", + "pk": "NA", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "NAMIBIA", + "sort_priority": 0, + "name_official": "NAMIBIA", "name": "Namibia" } - }, + }, { - "pk": "NR", - "model": "shipping.country", + "pk": "NR", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "NAURU", + "sort_priority": 0, + "name_official": "NAURU", "name": "Nauru" } - }, + }, { - "pk": "NP", - "model": "shipping.country", + "pk": "NP", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "NEPAL", + "sort_priority": 0, + "name_official": "NEPAL", "name": "Nepal" } - }, + }, { - "pk": "NL", - "model": "shipping.country", + "pk": "NL", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "NETHERLANDS", + "sort_priority": 0, + "name_official": "NETHERLANDS", "name": "Netherlands" } - }, + }, { - "pk": "AN", - "model": "shipping.country", + "pk": "AN", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "NETHERLANDS ANTILLES", + "sort_priority": 0, + "name_official": "NETHERLANDS ANTILLES", "name": "Netherlands Antilles" } - }, + }, { - "pk": "NC", - "model": "shipping.country", + "pk": "NC", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "NEW CALEDONIA", + "sort_priority": 0, + "name_official": "NEW CALEDONIA", "name": "New Caledonia" } - }, + }, { - "pk": "NZ", - "model": "shipping.country", + "pk": "NZ", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "NEW ZEALAND", + "sort_priority": 0, + "name_official": "NEW ZEALAND", "name": "New Zealand" } - }, + }, { - "pk": "NI", - "model": "shipping.country", + "pk": "NI", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "NICARAGUA", + "sort_priority": 0, + "name_official": "NICARAGUA", "name": "Nicaragua" } - }, + }, { - "pk": "NE", - "model": "shipping.country", + "pk": "NE", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "NIGER", + "sort_priority": 0, + "name_official": "NIGER", "name": "Niger" } - }, + }, { - "pk": "NG", - "model": "shipping.country", + "pk": "NG", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "NIGERIA", + "sort_priority": 0, + "name_official": "NIGERIA", "name": "Nigeria" } - }, + }, { - "pk": "NU", - "model": "shipping.country", + "pk": "NU", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "NIUE", + "sort_priority": 0, + "name_official": "NIUE", "name": "Niue" } - }, + }, { - "pk": "NF", - "model": "shipping.country", + "pk": "NF", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "NORFOLK ISLAND", + "sort_priority": 0, + "name_official": "NORFOLK ISLAND", "name": "Norfolk Island" } - }, + }, { - "pk": "MP", - "model": "shipping.country", + "pk": "MP", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "NORTHERN MARIANA ISLANDS", + "sort_priority": 0, + "name_official": "NORTHERN MARIANA ISLANDS", "name": "Northern Mariana Islands" } - }, + }, { - "pk": "NO", - "model": "shipping.country", + "pk": "NO", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "NORWAY", + "sort_priority": 0, + "name_official": "NORWAY", "name": "Norway" } - }, + }, { - "pk": "OM", - "model": "shipping.country", + "pk": "OM", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "OMAN", + "sort_priority": 0, + "name_official": "OMAN", "name": "Oman" } - }, + }, { - "pk": "PK", - "model": "shipping.country", + "pk": "PK", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "PAKISTAN", + "sort_priority": 0, + "name_official": "PAKISTAN", "name": "Pakistan" } - }, + }, { - "pk": "PW", - "model": "shipping.country", + "pk": "PW", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "PALAU", + "sort_priority": 0, + "name_official": "PALAU", "name": "Palau" } - }, + }, { - "pk": "PS", - "model": "shipping.country", + "pk": "PS", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "PALESTINIAN TERRITORY, OCCUPIED", + "sort_priority": 0, + "name_official": "PALESTINIAN TERRITORY, OCCUPIED", "name": "Palestinian Territory, Occupied" } - }, + }, { - "pk": "PA", - "model": "shipping.country", + "pk": "PA", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "PANAMA", + "sort_priority": 0, + "name_official": "PANAMA", "name": "Panama" } - }, + }, { - "pk": "PG", - "model": "shipping.country", + "pk": "PG", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "PAPUA NEW GUINEA", + "sort_priority": 0, + "name_official": "PAPUA NEW GUINEA", "name": "Papua New Guinea" } - }, + }, { - "pk": "PY", - "model": "shipping.country", + "pk": "PY", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "PARAGUAY", + "sort_priority": 0, + "name_official": "PARAGUAY", "name": "Paraguay" } - }, + }, { - "pk": "PE", - "model": "shipping.country", + "pk": "PE", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "PERU", + "sort_priority": 0, + "name_official": "PERU", "name": "Peru" } - }, + }, { - "pk": "PH", - "model": "shipping.country", + "pk": "PH", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "PHILIPPINES", + "sort_priority": 0, + "name_official": "PHILIPPINES", "name": "Philippines" } - }, + }, { - "pk": "PN", - "model": "shipping.country", + "pk": "PN", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "PITCAIRN", + "sort_priority": 0, + "name_official": "PITCAIRN", "name": "Pitcairn" } - }, + }, { - "pk": "PL", - "model": "shipping.country", + "pk": "PL", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "POLAND", + "sort_priority": 0, + "name_official": "POLAND", "name": "Poland" } - }, + }, { - "pk": "PT", - "model": "shipping.country", + "pk": "PT", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "PORTUGAL", + "sort_priority": 0, + "name_official": "PORTUGAL", "name": "Portugal" } - }, + }, { - "pk": "PR", - "model": "shipping.country", + "pk": "PR", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "PUERTO RICO", + "sort_priority": 0, + "name_official": "PUERTO RICO", "name": "Puerto Rico" } - }, + }, { - "pk": "QA", - "model": "shipping.country", + "pk": "QA", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "QATAR", + "sort_priority": 0, + "name_official": "QATAR", "name": "Qatar" } - }, + }, { - "pk": "RE", - "model": "shipping.country", + "pk": "RE", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "R\u00c9UNION", + "sort_priority": 0, + "name_official": "R\u00c9UNION", "name": "R\u00c9Union" } - }, + }, { - "pk": "RO", - "model": "shipping.country", + "pk": "RO", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ROMANIA", + "sort_priority": 0, + "name_official": "ROMANIA", "name": "Romania" } - }, + }, { - "pk": "RU", - "model": "shipping.country", + "pk": "RU", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "RUSSIAN FEDERATION", + "sort_priority": 0, + "name_official": "RUSSIAN FEDERATION", "name": "Russian Federation" } - }, + }, { - "pk": "RW", - "model": "shipping.country", + "pk": "RW", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "RWANDA", + "sort_priority": 0, + "name_official": "RWANDA", "name": "Rwanda" } - }, + }, { - "pk": "BL", - "model": "shipping.country", + "pk": "BL", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SAINT BARTH\u00c9LEMY", + "sort_priority": 0, + "name_official": "SAINT BARTH\u00c9LEMY", "name": "Saint Barth\u00c9Lemy" } - }, + }, { - "pk": "SH", - "model": "shipping.country", + "pk": "SH", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SAINT HELENA, ASCENSION AND TRISTAN DA CUNHA", + "sort_priority": 0, + "name_official": "SAINT HELENA, ASCENSION AND TRISTAN DA CUNHA", "name": "Saint Helena, Ascension And Tristan Da Cunha" } - }, + }, { - "pk": "KN", - "model": "shipping.country", + "pk": "KN", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SAINT KITTS AND NEVIS", + "sort_priority": 0, + "name_official": "SAINT KITTS AND NEVIS", "name": "Saint Kitts And Nevis" } - }, + }, { - "pk": "LC", - "model": "shipping.country", + "pk": "LC", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SAINT LUCIA", + "sort_priority": 0, + "name_official": "SAINT LUCIA", "name": "Saint Lucia" } - }, + }, { - "pk": "MF", - "model": "shipping.country", + "pk": "MF", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SAINT MARTIN", + "sort_priority": 0, + "name_official": "SAINT MARTIN", "name": "Saint Martin" } - }, + }, { - "pk": "PM", - "model": "shipping.country", + "pk": "PM", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SAINT PIERRE AND MIQUELON", + "sort_priority": 0, + "name_official": "SAINT PIERRE AND MIQUELON", "name": "Saint Pierre And Miquelon" } - }, + }, { - "pk": "VC", - "model": "shipping.country", + "pk": "VC", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SAINT VINCENT AND THE GRENADINES", + "sort_priority": 0, + "name_official": "SAINT VINCENT AND THE GRENADINES", "name": "Saint Vincent And The Grenadines" } - }, + }, { - "pk": "WS", - "model": "shipping.country", + "pk": "WS", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SAMOA", + "sort_priority": 0, + "name_official": "SAMOA", "name": "Samoa" } - }, + }, { - "pk": "SM", - "model": "shipping.country", + "pk": "SM", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SAN MARINO", + "sort_priority": 0, + "name_official": "SAN MARINO", "name": "San Marino" } - }, + }, { - "pk": "ST", - "model": "shipping.country", + "pk": "ST", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SAO TOME AND PRINCIPE", + "sort_priority": 0, + "name_official": "SAO TOME AND PRINCIPE", "name": "Sao Tome And Principe" } - }, + }, { - "pk": "SA", - "model": "shipping.country", + "pk": "SA", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SAUDI ARABIA", + "sort_priority": 0, + "name_official": "SAUDI ARABIA", "name": "Saudi Arabia" } - }, + }, { - "pk": "SN", - "model": "shipping.country", + "pk": "SN", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SENEGAL", + "sort_priority": 0, + "name_official": "SENEGAL", "name": "Senegal" } - }, + }, { - "pk": "RS", - "model": "shipping.country", + "pk": "RS", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SERBIA", + "sort_priority": 0, + "name_official": "SERBIA", "name": "Serbia" } - }, + }, { - "pk": "SC", - "model": "shipping.country", + "pk": "SC", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SEYCHELLES", + "sort_priority": 0, + "name_official": "SEYCHELLES", "name": "Seychelles" } - }, + }, { - "pk": "SL", - "model": "shipping.country", + "pk": "SL", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SIERRA LEONE", + "sort_priority": 0, + "name_official": "SIERRA LEONE", "name": "Sierra Leone" } - }, + }, { - "pk": "SG", - "model": "shipping.country", + "pk": "SG", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SINGAPORE", + "sort_priority": 0, + "name_official": "SINGAPORE", "name": "Singapore" } - }, + }, { - "pk": "SK", - "model": "shipping.country", + "pk": "SK", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SLOVAKIA", + "sort_priority": 0, + "name_official": "SLOVAKIA", "name": "Slovakia" } - }, + }, { - "pk": "SI", - "model": "shipping.country", + "pk": "SI", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SLOVENIA", + "sort_priority": 0, + "name_official": "SLOVENIA", "name": "Slovenia" } - }, + }, { - "pk": "SB", - "model": "shipping.country", + "pk": "SB", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SOLOMON ISLANDS", + "sort_priority": 0, + "name_official": "SOLOMON ISLANDS", "name": "Solomon Islands" } - }, + }, { - "pk": "SO", - "model": "shipping.country", + "pk": "SO", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SOMALIA", + "sort_priority": 0, + "name_official": "SOMALIA", "name": "Somalia" } - }, + }, { - "pk": "ZA", - "model": "shipping.country", + "pk": "ZA", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SOUTH AFRICA", + "sort_priority": 0, + "name_official": "SOUTH AFRICA", "name": "South Africa" } - }, + }, { - "pk": "GS", - "model": "shipping.country", + "pk": "GS", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS", + "sort_priority": 0, + "name_official": "SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS", "name": "South Georgia And The South Sandwich Islands" } - }, + }, { - "pk": "ES", - "model": "shipping.country", + "pk": "ES", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SPAIN", + "sort_priority": 0, + "name_official": "SPAIN", "name": "Spain" } - }, + }, { - "pk": "LK", - "model": "shipping.country", + "pk": "LK", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SRI LANKA", + "sort_priority": 0, + "name_official": "SRI LANKA", "name": "Sri Lanka" } - }, + }, { - "pk": "SD", - "model": "shipping.country", + "pk": "SD", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SUDAN", + "sort_priority": 0, + "name_official": "SUDAN", "name": "Sudan" } - }, + }, { - "pk": "SR", - "model": "shipping.country", + "pk": "SR", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SURINAME", + "sort_priority": 0, + "name_official": "SURINAME", "name": "Suriname" } - }, + }, { - "pk": "SJ", - "model": "shipping.country", + "pk": "SJ", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SVALBARD AND JAN MAYEN", + "sort_priority": 0, + "name_official": "SVALBARD AND JAN MAYEN", "name": "Svalbard And Jan Mayen" } - }, + }, { - "pk": "SZ", - "model": "shipping.country", + "pk": "SZ", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SWAZILAND", + "sort_priority": 0, + "name_official": "SWAZILAND", "name": "Swaziland" } - }, + }, { - "pk": "SE", - "model": "shipping.country", + "pk": "SE", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SWEDEN", + "sort_priority": 0, + "name_official": "SWEDEN", "name": "Sweden" } - }, + }, { - "pk": "CH", - "model": "shipping.country", + "pk": "CH", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SWITZERLAND", + "sort_priority": 0, + "name_official": "SWITZERLAND", "name": "Switzerland" } - }, + }, { - "pk": "SY", - "model": "shipping.country", + "pk": "SY", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "SYRIAN ARAB REPUBLIC", + "sort_priority": 0, + "name_official": "SYRIAN ARAB REPUBLIC", "name": "Syrian Arab Republic" } - }, + }, { - "pk": "TW", - "model": "shipping.country", + "pk": "TW", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "TAIWAN, PROVINCE OF CHINA", + "sort_priority": 0, + "name_official": "TAIWAN, PROVINCE OF CHINA", "name": "Taiwan, Province Of China" } - }, + }, { - "pk": "TJ", - "model": "shipping.country", + "pk": "TJ", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "TAJIKISTAN", + "sort_priority": 0, + "name_official": "TAJIKISTAN", "name": "Tajikistan" } - }, + }, { - "pk": "TZ", - "model": "shipping.country", + "pk": "TZ", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "TANZANIA, UNITED REPUBLIC OF", + "sort_priority": 0, + "name_official": "TANZANIA, UNITED REPUBLIC OF", "name": "Tanzania, United Republic Of" } - }, + }, { - "pk": "TH", - "model": "shipping.country", + "pk": "TH", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "THAILAND", + "sort_priority": 0, + "name_official": "THAILAND", "name": "Thailand" } - }, + }, { - "pk": "TL", - "model": "shipping.country", + "pk": "TL", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "TIMOR-LESTE", + "sort_priority": 0, + "name_official": "TIMOR-LESTE", "name": "Timor-Leste" } - }, + }, { - "pk": "TG", - "model": "shipping.country", + "pk": "TG", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "TOGO", + "sort_priority": 0, + "name_official": "TOGO", "name": "Togo" } - }, + }, { - "pk": "TK", - "model": "shipping.country", + "pk": "TK", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "TOKELAU", + "sort_priority": 0, + "name_official": "TOKELAU", "name": "Tokelau" } - }, + }, { - "pk": "TO", - "model": "shipping.country", + "pk": "TO", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "TONGA", + "sort_priority": 0, + "name_official": "TONGA", "name": "Tonga" } - }, + }, { - "pk": "TT", - "model": "shipping.country", + "pk": "TT", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "TRINIDAD AND TOBAGO", + "sort_priority": 0, + "name_official": "TRINIDAD AND TOBAGO", "name": "Trinidad And Tobago" } - }, + }, { - "pk": "TN", - "model": "shipping.country", + "pk": "TN", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "TUNISIA", + "sort_priority": 0, + "name_official": "TUNISIA", "name": "Tunisia" } - }, + }, { - "pk": "TR", - "model": "shipping.country", + "pk": "TR", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "TURKEY", + "sort_priority": 0, + "name_official": "TURKEY", "name": "Turkey" } - }, + }, { - "pk": "TM", - "model": "shipping.country", + "pk": "TM", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "TURKMENISTAN", + "sort_priority": 0, + "name_official": "TURKMENISTAN", "name": "Turkmenistan" } - }, + }, { - "pk": "TC", - "model": "shipping.country", + "pk": "TC", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "TURKS AND CAICOS ISLANDS", + "sort_priority": 0, + "name_official": "TURKS AND CAICOS ISLANDS", "name": "Turks And Caicos Islands" } - }, + }, { - "pk": "TV", - "model": "shipping.country", + "pk": "TV", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "TUVALU", + "sort_priority": 0, + "name_official": "TUVALU", "name": "Tuvalu" } - }, + }, { - "pk": "UG", - "model": "shipping.country", + "pk": "UG", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "UGANDA", + "sort_priority": 0, + "name_official": "UGANDA", "name": "Uganda" } - }, + }, { - "pk": "UA", - "model": "shipping.country", + "pk": "UA", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "UKRAINE", + "sort_priority": 0, + "name_official": "UKRAINE", "name": "Ukraine" } - }, + }, { - "pk": "AE", - "model": "shipping.country", + "pk": "AE", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "UNITED ARAB EMIRATES", + "sort_priority": 0, + "name_official": "UNITED ARAB EMIRATES", "name": "United Arab Emirates" } - }, + }, { - "pk": "GB", - "model": "shipping.country", + "pk": "GB", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "UNITED KINGDOM", + "sort_priority": 0, + "name_official": "UNITED KINGDOM", "name": "United Kingdom" } - }, + }, { - "pk": "US", - "model": "shipping.country", + "pk": "US", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "UNITED STATES", + "sort_priority": 0, + "name_official": "UNITED STATES", "name": "United States" } - }, + }, { - "pk": "UM", - "model": "shipping.country", + "pk": "UM", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "UNITED STATES MINOR OUTLYING ISLANDS", + "sort_priority": 0, + "name_official": "UNITED STATES MINOR OUTLYING ISLANDS", "name": "United States Minor Outlying Islands" } - }, + }, { - "pk": "UY", - "model": "shipping.country", + "pk": "UY", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "URUGUAY", + "sort_priority": 0, + "name_official": "URUGUAY", "name": "Uruguay" } - }, + }, { - "pk": "UZ", - "model": "shipping.country", + "pk": "UZ", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "UZBEKISTAN", + "sort_priority": 0, + "name_official": "UZBEKISTAN", "name": "Uzbekistan" } - }, + }, { - "pk": "VU", - "model": "shipping.country", + "pk": "VU", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "VANUATU", + "sort_priority": 0, + "name_official": "VANUATU", "name": "Vanuatu" } - }, + }, { - "pk": "VE", - "model": "shipping.country", + "pk": "VE", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "VENEZUELA, BOLIVARIAN REPUBLIC OF", + "sort_priority": 0, + "name_official": "VENEZUELA, BOLIVARIAN REPUBLIC OF", "name": "Venezuela, Bolivarian Republic Of" } - }, + }, { - "pk": "VN", - "model": "shipping.country", + "pk": "VN", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "VIET NAM", + "sort_priority": 0, + "name_official": "VIET NAM", "name": "Viet Nam" } - }, + }, { - "pk": "VG", - "model": "shipping.country", + "pk": "VG", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "VIRGIN ISLANDS, BRITISH", + "sort_priority": 0, + "name_official": "VIRGIN ISLANDS, BRITISH", "name": "Virgin Islands, British" } - }, + }, { - "pk": "VI", - "model": "shipping.country", + "pk": "VI", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "VIRGIN ISLANDS, U.S.", + "sort_priority": 0, + "name_official": "VIRGIN ISLANDS, U.S.", "name": "Virgin Islands, U.S." } - }, + }, { - "pk": "WF", - "model": "shipping.country", + "pk": "WF", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "WALLIS AND FUTUNA", + "sort_priority": 0, + "name_official": "WALLIS AND FUTUNA", "name": "Wallis And Futuna" } - }, + }, { - "pk": "EH", - "model": "shipping.country", + "pk": "EH", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "WESTERN SAHARA", + "sort_priority": 0, + "name_official": "WESTERN SAHARA", "name": "Western Sahara" } - }, + }, { - "pk": "YE", - "model": "shipping.country", + "pk": "YE", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "YEMEN", + "sort_priority": 0, + "name_official": "YEMEN", "name": "Yemen" } - }, + }, { - "pk": "ZM", - "model": "shipping.country", + "pk": "ZM", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ZAMBIA", + "sort_priority": 0, + "name_official": "ZAMBIA", "name": "Zambia" } - }, + }, { - "pk": "ZW", - "model": "shipping.country", + "pk": "ZW", + "model": "longclaw_shipping.country", "fields": { - "sort_priority": 0, - "name_official": "ZIMBABWE", + "sort_priority": 0, + "name_official": "ZIMBABWE", "name": "Zimbabwe" } } -] \ No newline at end of file +] diff --git a/longclaw/shipping/fixtures/shipping_initial_latest.json b/longclaw/shipping/fixtures/shipping_initial_latest.json new file mode 100644 index 00000000..c8e23f7e --- /dev/null +++ b/longclaw/shipping/fixtures/shipping_initial_latest.json @@ -0,0 +1,2216 @@ +[ +{ + "model": "longclaw_shipping.country", + "pk": "AD", + "fields": { + "name_official": "ANDORRA", + "name": "Andorra", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "AE", + "fields": { + "name_official": "UNITED ARAB EMIRATES", + "name": "United Arab Emirates", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "AF", + "fields": { + "name_official": "AFGHANISTAN", + "name": "Afghanistan", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "AG", + "fields": { + "name_official": "ANTIGUA AND BARBUDA", + "name": "Antigua And Barbuda", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "AI", + "fields": { + "name_official": "ANGUILLA", + "name": "Anguilla", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "AL", + "fields": { + "name_official": "ALBANIA", + "name": "Albania", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "AM", + "fields": { + "name_official": "ARMENIA", + "name": "Armenia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "AN", + "fields": { + "name_official": "NETHERLANDS ANTILLES", + "name": "Netherlands Antilles", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "AO", + "fields": { + "name_official": "ANGOLA", + "name": "Angola", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "AQ", + "fields": { + "name_official": "ANTARCTICA", + "name": "Antarctica", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "AR", + "fields": { + "name_official": "ARGENTINA", + "name": "Argentina", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "AS", + "fields": { + "name_official": "AMERICAN SAMOA", + "name": "American Samoa", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "AT", + "fields": { + "name_official": "AUSTRIA", + "name": "Austria", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "AU", + "fields": { + "name_official": "AUSTRALIA", + "name": "Australia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "AW", + "fields": { + "name_official": "ARUBA", + "name": "Aruba", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "AX", + "fields": { + "name_official": "ÅLAND ISLANDS", + "name": "ÅLand Islands", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "AZ", + "fields": { + "name_official": "AZERBAIJAN", + "name": "Azerbaijan", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "BA", + "fields": { + "name_official": "BOSNIA AND HERZEGOVINA", + "name": "Bosnia And Herzegovina", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "BB", + "fields": { + "name_official": "BARBADOS", + "name": "Barbados", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "BD", + "fields": { + "name_official": "BANGLADESH", + "name": "Bangladesh", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "BE", + "fields": { + "name_official": "BELGIUM", + "name": "Belgium", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "BF", + "fields": { + "name_official": "BURKINA FASO", + "name": "Burkina Faso", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "BG", + "fields": { + "name_official": "BULGARIA", + "name": "Bulgaria", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "BH", + "fields": { + "name_official": "BAHRAIN", + "name": "Bahrain", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "BI", + "fields": { + "name_official": "BURUNDI", + "name": "Burundi", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "BJ", + "fields": { + "name_official": "BENIN", + "name": "Benin", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "BL", + "fields": { + "name_official": "SAINT BARTHÉLEMY", + "name": "Saint BarthÉLemy", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "BM", + "fields": { + "name_official": "BERMUDA", + "name": "Bermuda", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "BN", + "fields": { + "name_official": "BRUNEI DARUSSALAM", + "name": "Brunei Darussalam", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "BO", + "fields": { + "name_official": "BOLIVIA, PLURINATIONAL STATE OF", + "name": "Bolivia, Plurinational State Of", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "BR", + "fields": { + "name_official": "BRAZIL", + "name": "Brazil", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "BS", + "fields": { + "name_official": "BAHAMAS", + "name": "Bahamas", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "BT", + "fields": { + "name_official": "BHUTAN", + "name": "Bhutan", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "BV", + "fields": { + "name_official": "BOUVET ISLAND", + "name": "Bouvet Island", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "BW", + "fields": { + "name_official": "BOTSWANA", + "name": "Botswana", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "BY", + "fields": { + "name_official": "BELARUS", + "name": "Belarus", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "BZ", + "fields": { + "name_official": "BELIZE", + "name": "Belize", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "CA", + "fields": { + "name_official": "CANADA", + "name": "Canada", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "CC", + "fields": { + "name_official": "COCOS (KEELING) ISLANDS", + "name": "Cocos (Keeling) Islands", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "CD", + "fields": { + "name_official": "CONGO, THE DEMOCRATIC REPUBLIC OF THE", + "name": "Congo, The Democratic Republic Of The", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "CF", + "fields": { + "name_official": "CENTRAL AFRICAN REPUBLIC", + "name": "Central African Republic", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "CG", + "fields": { + "name_official": "CONGO", + "name": "Congo", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "CH", + "fields": { + "name_official": "SWITZERLAND", + "name": "Switzerland", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "CI", + "fields": { + "name_official": "CÔTE D'IVOIRE", + "name": "CÔTe D'Ivoire", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "CK", + "fields": { + "name_official": "COOK ISLANDS", + "name": "Cook Islands", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "CL", + "fields": { + "name_official": "CHILE", + "name": "Chile", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "CM", + "fields": { + "name_official": "CAMEROON", + "name": "Cameroon", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "CN", + "fields": { + "name_official": "CHINA", + "name": "China", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "CO", + "fields": { + "name_official": "COLOMBIA", + "name": "Colombia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "CR", + "fields": { + "name_official": "COSTA RICA", + "name": "Costa Rica", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "CU", + "fields": { + "name_official": "CUBA", + "name": "Cuba", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "CV", + "fields": { + "name_official": "CAPE VERDE", + "name": "Cape Verde", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "CX", + "fields": { + "name_official": "CHRISTMAS ISLAND", + "name": "Christmas Island", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "CY", + "fields": { + "name_official": "CYPRUS", + "name": "Cyprus", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "CZ", + "fields": { + "name_official": "CZECH REPUBLIC", + "name": "Czech Republic", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "DE", + "fields": { + "name_official": "GERMANY", + "name": "Germany", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "DJ", + "fields": { + "name_official": "DJIBOUTI", + "name": "Djibouti", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "DK", + "fields": { + "name_official": "DENMARK", + "name": "Denmark", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "DM", + "fields": { + "name_official": "DOMINICA", + "name": "Dominica", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "DO", + "fields": { + "name_official": "DOMINICAN REPUBLIC", + "name": "Dominican Republic", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "DZ", + "fields": { + "name_official": "ALGERIA", + "name": "Algeria", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "EC", + "fields": { + "name_official": "ECUADOR", + "name": "Ecuador", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "EE", + "fields": { + "name_official": "ESTONIA", + "name": "Estonia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "EG", + "fields": { + "name_official": "EGYPT", + "name": "Egypt", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "EH", + "fields": { + "name_official": "WESTERN SAHARA", + "name": "Western Sahara", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "ER", + "fields": { + "name_official": "ERITREA", + "name": "Eritrea", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "ES", + "fields": { + "name_official": "SPAIN", + "name": "Spain", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "ET", + "fields": { + "name_official": "ETHIOPIA", + "name": "Ethiopia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "FI", + "fields": { + "name_official": "FINLAND", + "name": "Finland", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "FJ", + "fields": { + "name_official": "FIJI", + "name": "Fiji", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "FK", + "fields": { + "name_official": "FALKLAND ISLANDS (MALVINAS)", + "name": "Falkland Islands (Malvinas)", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "FM", + "fields": { + "name_official": "MICRONESIA, FEDERATED STATES OF", + "name": "Micronesia, Federated States Of", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "FO", + "fields": { + "name_official": "FAROE ISLANDS", + "name": "Faroe Islands", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "FR", + "fields": { + "name_official": "FRANCE", + "name": "France", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "GA", + "fields": { + "name_official": "GABON", + "name": "Gabon", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "GB", + "fields": { + "name_official": "UNITED KINGDOM", + "name": "United Kingdom", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "GD", + "fields": { + "name_official": "GRENADA", + "name": "Grenada", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "GE", + "fields": { + "name_official": "GEORGIA", + "name": "Georgia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "GF", + "fields": { + "name_official": "FRENCH GUIANA", + "name": "French Guiana", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "GG", + "fields": { + "name_official": "GUERNSEY", + "name": "Guernsey", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "GH", + "fields": { + "name_official": "GHANA", + "name": "Ghana", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "GI", + "fields": { + "name_official": "GIBRALTAR", + "name": "Gibraltar", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "GL", + "fields": { + "name_official": "GREENLAND", + "name": "Greenland", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "GM", + "fields": { + "name_official": "GAMBIA", + "name": "Gambia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "GN", + "fields": { + "name_official": "GUINEA", + "name": "Guinea", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "GP", + "fields": { + "name_official": "GUADELOUPE", + "name": "Guadeloupe", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "GQ", + "fields": { + "name_official": "EQUATORIAL GUINEA", + "name": "Equatorial Guinea", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "GR", + "fields": { + "name_official": "GREECE", + "name": "Greece", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "GS", + "fields": { + "name_official": "SOUTH GEORGIA AND THE SOUTH SANDWICH ISLANDS", + "name": "South Georgia And The South Sandwich Islands", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "GT", + "fields": { + "name_official": "GUATEMALA", + "name": "Guatemala", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "GU", + "fields": { + "name_official": "GUAM", + "name": "Guam", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "GW", + "fields": { + "name_official": "GUINEA-BISSAU", + "name": "Guinea-Bissau", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "GY", + "fields": { + "name_official": "GUYANA", + "name": "Guyana", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "HK", + "fields": { + "name_official": "HONG KONG", + "name": "Hong Kong", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "HM", + "fields": { + "name_official": "HEARD ISLAND AND MCDONALD ISLANDS", + "name": "Heard Island And Mcdonald Islands", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "HN", + "fields": { + "name_official": "HONDURAS", + "name": "Honduras", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "HR", + "fields": { + "name_official": "CROATIA", + "name": "Croatia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "HT", + "fields": { + "name_official": "HAITI", + "name": "Haiti", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "HU", + "fields": { + "name_official": "HUNGARY", + "name": "Hungary", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "ID", + "fields": { + "name_official": "INDONESIA", + "name": "Indonesia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "IE", + "fields": { + "name_official": "IRELAND", + "name": "Ireland", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "IL", + "fields": { + "name_official": "ISRAEL", + "name": "Israel", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "IM", + "fields": { + "name_official": "ISLE OF MAN", + "name": "Isle Of Man", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "IN", + "fields": { + "name_official": "INDIA", + "name": "India", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "IO", + "fields": { + "name_official": "BRITISH INDIAN OCEAN TERRITORY", + "name": "British Indian Ocean Territory", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "IQ", + "fields": { + "name_official": "IRAQ", + "name": "Iraq", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "IR", + "fields": { + "name_official": "IRAN, ISLAMIC REPUBLIC OF", + "name": "Iran, Islamic Republic Of", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "IS", + "fields": { + "name_official": "ICELAND", + "name": "Iceland", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "IT", + "fields": { + "name_official": "ITALY", + "name": "Italy", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "JE", + "fields": { + "name_official": "JERSEY", + "name": "Jersey", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "JM", + "fields": { + "name_official": "JAMAICA", + "name": "Jamaica", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "JO", + "fields": { + "name_official": "JORDAN", + "name": "Jordan", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "JP", + "fields": { + "name_official": "JAPAN", + "name": "Japan", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "KE", + "fields": { + "name_official": "KENYA", + "name": "Kenya", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "KG", + "fields": { + "name_official": "KYRGYZSTAN", + "name": "Kyrgyzstan", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "KH", + "fields": { + "name_official": "CAMBODIA", + "name": "Cambodia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "KI", + "fields": { + "name_official": "KIRIBATI", + "name": "Kiribati", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "KM", + "fields": { + "name_official": "COMOROS", + "name": "Comoros", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "KN", + "fields": { + "name_official": "SAINT KITTS AND NEVIS", + "name": "Saint Kitts And Nevis", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "KP", + "fields": { + "name_official": "KOREA, DEMOCRATIC PEOPLE'S REPUBLIC OF", + "name": "Korea, Democratic People'S Republic Of", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "KR", + "fields": { + "name_official": "KOREA, REPUBLIC OF", + "name": "Korea, Republic Of", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "KW", + "fields": { + "name_official": "KUWAIT", + "name": "Kuwait", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "KY", + "fields": { + "name_official": "CAYMAN ISLANDS", + "name": "Cayman Islands", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "KZ", + "fields": { + "name_official": "KAZAKHSTAN", + "name": "Kazakhstan", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "LA", + "fields": { + "name_official": "LAO PEOPLE'S DEMOCRATIC REPUBLIC", + "name": "Lao People'S Democratic Republic", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "LB", + "fields": { + "name_official": "LEBANON", + "name": "Lebanon", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "LC", + "fields": { + "name_official": "SAINT LUCIA", + "name": "Saint Lucia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "LI", + "fields": { + "name_official": "LIECHTENSTEIN", + "name": "Liechtenstein", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "LK", + "fields": { + "name_official": "SRI LANKA", + "name": "Sri Lanka", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "LR", + "fields": { + "name_official": "LIBERIA", + "name": "Liberia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "LS", + "fields": { + "name_official": "LESOTHO", + "name": "Lesotho", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "LT", + "fields": { + "name_official": "LITHUANIA", + "name": "Lithuania", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "LU", + "fields": { + "name_official": "LUXEMBOURG", + "name": "Luxembourg", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "LV", + "fields": { + "name_official": "LATVIA", + "name": "Latvia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "LY", + "fields": { + "name_official": "LIBYAN ARAB JAMAHIRIYA", + "name": "Libyan Arab Jamahiriya", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MA", + "fields": { + "name_official": "MOROCCO", + "name": "Morocco", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MC", + "fields": { + "name_official": "MONACO", + "name": "Monaco", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MD", + "fields": { + "name_official": "MOLDOVA, REPUBLIC OF", + "name": "Moldova, Republic Of", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "ME", + "fields": { + "name_official": "MONTENEGRO", + "name": "Montenegro", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MF", + "fields": { + "name_official": "SAINT MARTIN", + "name": "Saint Martin", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MG", + "fields": { + "name_official": "MADAGASCAR", + "name": "Madagascar", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MH", + "fields": { + "name_official": "MARSHALL ISLANDS", + "name": "Marshall Islands", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MK", + "fields": { + "name_official": "MACEDONIA, THE FORMER YUGOSLAV REPUBLIC OF", + "name": "Macedonia, The Former Yugoslav Republic Of", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "ML", + "fields": { + "name_official": "MALI", + "name": "Mali", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MM", + "fields": { + "name_official": "MYANMAR", + "name": "Myanmar", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MN", + "fields": { + "name_official": "MONGOLIA", + "name": "Mongolia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MO", + "fields": { + "name_official": "MACAO", + "name": "Macao", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MP", + "fields": { + "name_official": "NORTHERN MARIANA ISLANDS", + "name": "Northern Mariana Islands", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MQ", + "fields": { + "name_official": "MARTINIQUE", + "name": "Martinique", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MR", + "fields": { + "name_official": "MAURITANIA", + "name": "Mauritania", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MS", + "fields": { + "name_official": "MONTSERRAT", + "name": "Montserrat", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MT", + "fields": { + "name_official": "MALTA", + "name": "Malta", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MU", + "fields": { + "name_official": "MAURITIUS", + "name": "Mauritius", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MV", + "fields": { + "name_official": "MALDIVES", + "name": "Maldives", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MW", + "fields": { + "name_official": "MALAWI", + "name": "Malawi", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MX", + "fields": { + "name_official": "MEXICO", + "name": "Mexico", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MY", + "fields": { + "name_official": "MALAYSIA", + "name": "Malaysia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "MZ", + "fields": { + "name_official": "MOZAMBIQUE", + "name": "Mozambique", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "NA", + "fields": { + "name_official": "NAMIBIA", + "name": "Namibia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "NC", + "fields": { + "name_official": "NEW CALEDONIA", + "name": "New Caledonia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "NE", + "fields": { + "name_official": "NIGER", + "name": "Niger", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "NF", + "fields": { + "name_official": "NORFOLK ISLAND", + "name": "Norfolk Island", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "NG", + "fields": { + "name_official": "NIGERIA", + "name": "Nigeria", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "NI", + "fields": { + "name_official": "NICARAGUA", + "name": "Nicaragua", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "NL", + "fields": { + "name_official": "NETHERLANDS", + "name": "Netherlands", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "NO", + "fields": { + "name_official": "NORWAY", + "name": "Norway", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "NP", + "fields": { + "name_official": "NEPAL", + "name": "Nepal", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "NR", + "fields": { + "name_official": "NAURU", + "name": "Nauru", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "NU", + "fields": { + "name_official": "NIUE", + "name": "Niue", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "NZ", + "fields": { + "name_official": "NEW ZEALAND", + "name": "New Zealand", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "OM", + "fields": { + "name_official": "OMAN", + "name": "Oman", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "PA", + "fields": { + "name_official": "PANAMA", + "name": "Panama", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "PE", + "fields": { + "name_official": "PERU", + "name": "Peru", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "PF", + "fields": { + "name_official": "FRENCH POLYNESIA", + "name": "French Polynesia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "PG", + "fields": { + "name_official": "PAPUA NEW GUINEA", + "name": "Papua New Guinea", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "PH", + "fields": { + "name_official": "PHILIPPINES", + "name": "Philippines", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "PK", + "fields": { + "name_official": "PAKISTAN", + "name": "Pakistan", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "PL", + "fields": { + "name_official": "POLAND", + "name": "Poland", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "PM", + "fields": { + "name_official": "SAINT PIERRE AND MIQUELON", + "name": "Saint Pierre And Miquelon", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "PN", + "fields": { + "name_official": "PITCAIRN", + "name": "Pitcairn", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "PR", + "fields": { + "name_official": "PUERTO RICO", + "name": "Puerto Rico", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "PS", + "fields": { + "name_official": "PALESTINIAN TERRITORY, OCCUPIED", + "name": "Palestinian Territory, Occupied", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "PT", + "fields": { + "name_official": "PORTUGAL", + "name": "Portugal", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "PW", + "fields": { + "name_official": "PALAU", + "name": "Palau", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "PY", + "fields": { + "name_official": "PARAGUAY", + "name": "Paraguay", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "QA", + "fields": { + "name_official": "QATAR", + "name": "Qatar", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "RE", + "fields": { + "name_official": "RÉUNION", + "name": "RÉUnion", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "RO", + "fields": { + "name_official": "ROMANIA", + "name": "Romania", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "RS", + "fields": { + "name_official": "SERBIA", + "name": "Serbia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "RU", + "fields": { + "name_official": "RUSSIAN FEDERATION", + "name": "Russian Federation", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "RW", + "fields": { + "name_official": "RWANDA", + "name": "Rwanda", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "SA", + "fields": { + "name_official": "SAUDI ARABIA", + "name": "Saudi Arabia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "SB", + "fields": { + "name_official": "SOLOMON ISLANDS", + "name": "Solomon Islands", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "SC", + "fields": { + "name_official": "SEYCHELLES", + "name": "Seychelles", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "SD", + "fields": { + "name_official": "SUDAN", + "name": "Sudan", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "SE", + "fields": { + "name_official": "SWEDEN", + "name": "Sweden", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "SG", + "fields": { + "name_official": "SINGAPORE", + "name": "Singapore", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "SH", + "fields": { + "name_official": "SAINT HELENA, ASCENSION AND TRISTAN DA CUNHA", + "name": "Saint Helena, Ascension And Tristan Da Cunha", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "SI", + "fields": { + "name_official": "SLOVENIA", + "name": "Slovenia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "SJ", + "fields": { + "name_official": "SVALBARD AND JAN MAYEN", + "name": "Svalbard And Jan Mayen", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "SK", + "fields": { + "name_official": "SLOVAKIA", + "name": "Slovakia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "SL", + "fields": { + "name_official": "SIERRA LEONE", + "name": "Sierra Leone", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "SM", + "fields": { + "name_official": "SAN MARINO", + "name": "San Marino", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "SN", + "fields": { + "name_official": "SENEGAL", + "name": "Senegal", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "SO", + "fields": { + "name_official": "SOMALIA", + "name": "Somalia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "SR", + "fields": { + "name_official": "SURINAME", + "name": "Suriname", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "ST", + "fields": { + "name_official": "SAO TOME AND PRINCIPE", + "name": "Sao Tome And Principe", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "SV", + "fields": { + "name_official": "EL SALVADOR", + "name": "El Salvador", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "SY", + "fields": { + "name_official": "SYRIAN ARAB REPUBLIC", + "name": "Syrian Arab Republic", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "SZ", + "fields": { + "name_official": "SWAZILAND", + "name": "Swaziland", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "TC", + "fields": { + "name_official": "TURKS AND CAICOS ISLANDS", + "name": "Turks And Caicos Islands", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "TD", + "fields": { + "name_official": "CHAD", + "name": "Chad", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "TF", + "fields": { + "name_official": "FRENCH SOUTHERN TERRITORIES", + "name": "French Southern Territories", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "TG", + "fields": { + "name_official": "TOGO", + "name": "Togo", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "TH", + "fields": { + "name_official": "THAILAND", + "name": "Thailand", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "TJ", + "fields": { + "name_official": "TAJIKISTAN", + "name": "Tajikistan", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "TK", + "fields": { + "name_official": "TOKELAU", + "name": "Tokelau", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "TL", + "fields": { + "name_official": "TIMOR-LESTE", + "name": "Timor-Leste", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "TM", + "fields": { + "name_official": "TURKMENISTAN", + "name": "Turkmenistan", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "TN", + "fields": { + "name_official": "TUNISIA", + "name": "Tunisia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "TO", + "fields": { + "name_official": "TONGA", + "name": "Tonga", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "TR", + "fields": { + "name_official": "TURKEY", + "name": "Turkey", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "TT", + "fields": { + "name_official": "TRINIDAD AND TOBAGO", + "name": "Trinidad And Tobago", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "TV", + "fields": { + "name_official": "TUVALU", + "name": "Tuvalu", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "TW", + "fields": { + "name_official": "TAIWAN, PROVINCE OF CHINA", + "name": "Taiwan, Province Of China", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "TZ", + "fields": { + "name_official": "TANZANIA, UNITED REPUBLIC OF", + "name": "Tanzania, United Republic Of", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "UA", + "fields": { + "name_official": "UKRAINE", + "name": "Ukraine", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "UG", + "fields": { + "name_official": "UGANDA", + "name": "Uganda", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "UM", + "fields": { + "name_official": "UNITED STATES MINOR OUTLYING ISLANDS", + "name": "United States Minor Outlying Islands", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "US", + "fields": { + "name_official": "UNITED STATES", + "name": "United States", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "UY", + "fields": { + "name_official": "URUGUAY", + "name": "Uruguay", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "UZ", + "fields": { + "name_official": "UZBEKISTAN", + "name": "Uzbekistan", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "VA", + "fields": { + "name_official": "HOLY SEE (VATICAN CITY STATE)", + "name": "Holy See (Vatican City State)", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "VC", + "fields": { + "name_official": "SAINT VINCENT AND THE GRENADINES", + "name": "Saint Vincent And The Grenadines", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "VE", + "fields": { + "name_official": "VENEZUELA, BOLIVARIAN REPUBLIC OF", + "name": "Venezuela, Bolivarian Republic Of", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "VG", + "fields": { + "name_official": "VIRGIN ISLANDS, BRITISH", + "name": "Virgin Islands, British", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "VI", + "fields": { + "name_official": "VIRGIN ISLANDS, U.S.", + "name": "Virgin Islands, U.S.", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "VN", + "fields": { + "name_official": "VIET NAM", + "name": "Viet Nam", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "VU", + "fields": { + "name_official": "VANUATU", + "name": "Vanuatu", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "WF", + "fields": { + "name_official": "WALLIS AND FUTUNA", + "name": "Wallis And Futuna", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "WS", + "fields": { + "name_official": "SAMOA", + "name": "Samoa", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "YE", + "fields": { + "name_official": "YEMEN", + "name": "Yemen", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "YT", + "fields": { + "name_official": "MAYOTTE", + "name": "Mayotte", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "ZA", + "fields": { + "name_official": "SOUTH AFRICA", + "name": "South Africa", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "ZM", + "fields": { + "name_official": "ZAMBIA", + "name": "Zambia", + "sort_priority": 0 + } +}, +{ + "model": "longclaw_shipping.country", + "pk": "ZW", + "fields": { + "name_official": "ZIMBABWE", + "name": "Zimbabwe", + "sort_priority": 0 + } +} +] diff --git a/longclaw/shipping/forms.py b/longclaw/shipping/forms.py index 21136322..9fed4e32 100644 --- a/longclaw/shipping/forms.py +++ b/longclaw/shipping/forms.py @@ -1,14 +1,16 @@ -from django.forms import ModelForm, ModelChoiceField +from django.forms import ModelChoiceField, ModelForm + from longclaw.configuration.models import Configuration from longclaw.shipping.models import Address, Country + class AddressForm(ModelForm): class Meta: model = Address - fields = ['name', 'line_1', 'line_2', 'city', 'postcode', 'country'] + fields = ["name", "line_1", "line_2", "city", "postcode", "country"] def __init__(self, *args, **kwargs): - site = kwargs.pop('site', None) + site = kwargs.pop("site", None) super(AddressForm, self).__init__(*args, **kwargs) # Edit the country field to only contain @@ -21,5 +23,4 @@ def __init__(self, *args, **kwargs): queryset = Country.objects.all() else: queryset = Country.objects.exclude(shippingrate=None) - self.fields['country'] = ModelChoiceField(queryset) - + self.fields["country"] = ModelChoiceField(queryset) diff --git a/longclaw/checkout/migrations/__init__.py b/longclaw/shipping/management/__init__.py similarity index 100% rename from longclaw/checkout/migrations/__init__.py rename to longclaw/shipping/management/__init__.py diff --git a/longclaw/project_template/catalog/__init__.py b/longclaw/shipping/management/commands/__init__.py similarity index 100% rename from longclaw/project_template/catalog/__init__.py rename to longclaw/shipping/management/commands/__init__.py diff --git a/longclaw/shipping/management/commands/loadcountries.py b/longclaw/shipping/management/commands/loadcountries.py index 4351e813..125238ad 100644 --- a/longclaw/shipping/management/commands/loadcountries.py +++ b/longclaw/shipping/management/commands/loadcountries.py @@ -1,9 +1,14 @@ -from django.core.management.base import BaseCommand from django.core.management import call_command +from django.core.management.base import BaseCommand + class Command(BaseCommand): - help = 'Load world country data' + help = "Load world country data" def handle(self, *args, **options): - call_command('loaddata', 'shipping_initial.json', app_label='shipping') - self.stdout.write(self.style.SUCCESS('Initial country data was loaded to the database')) + call_command( + "loaddata", "shipping_initial_latest.json", app_label="longclaw_shipping" + ) + self.stdout.write( + self.style.SUCCESS("Initial country data was loaded to the database") + ) diff --git a/longclaw/shipping/migrations/0001_initial.py b/longclaw/shipping/migrations/0001_initial.py index 1b411856..0bdac4e1 100644 --- a/longclaw/shipping/migrations/0001_initial.py +++ b/longclaw/shipping/migrations/0001_initial.py @@ -1,55 +1,85 @@ # Generated by Django 2.1.4 on 2018-12-22 14:49 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='Address', + name="Address", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=64)), - ('line_1', models.CharField(max_length=128)), - ('line_2', models.CharField(blank=True, max_length=128)), - ('city', models.CharField(max_length=64)), - ('postcode', models.CharField(max_length=10)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=64)), + ("line_1", models.CharField(max_length=128)), + ("line_2", models.CharField(blank=True, max_length=128)), + ("city", models.CharField(max_length=64)), + ("postcode", models.CharField(max_length=10)), ], ), migrations.CreateModel( - name='Country', + name="Country", fields=[ - ('iso', models.CharField(max_length=2, primary_key=True, serialize=False)), - ('name_official', models.CharField(max_length=128)), - ('name', models.CharField(max_length=128)), - ('sort_priority', models.PositiveIntegerField(default=0)), + ( + "iso", + models.CharField(max_length=2, primary_key=True, serialize=False), + ), + ("name_official", models.CharField(max_length=128)), + ("name", models.CharField(max_length=128)), + ("sort_priority", models.PositiveIntegerField(default=0)), ], options={ - 'verbose_name_plural': 'Countries', - 'ordering': ('-sort_priority', 'name'), + "verbose_name_plural": "Countries", + "ordering": ("-sort_priority", "name"), }, ), migrations.CreateModel( - name='ShippingRate', + name="ShippingRate", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(help_text='Unique name to refer to this shipping rate by', max_length=32, unique=True)), - ('rate', models.DecimalField(decimal_places=2, max_digits=12)), - ('carrier', models.CharField(max_length=64)), - ('description', models.CharField(max_length=128)), - ('countries', models.ManyToManyField(to='shipping.Country')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.CharField( + help_text="Unique name to refer to this shipping rate by", + max_length=32, + unique=True, + ), + ), + ("rate", models.DecimalField(decimal_places=2, max_digits=12)), + ("carrier", models.CharField(max_length=64)), + ("description", models.CharField(max_length=128)), + ("countries", models.ManyToManyField(to="longclaw_shipping.Country")), ], ), migrations.AddField( - model_name='address', - name='country', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='shipping.Country'), + model_name="address", + name="country", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="longclaw_shipping.Country", + ), ), ] diff --git a/longclaw/shipping/migrations/0002_auto_20190318_1237.py b/longclaw/shipping/migrations/0002_auto_20190318_1237.py index 11ddeaa7..16df66be 100644 --- a/longclaw/shipping/migrations/0002_auto_20190318_1237.py +++ b/longclaw/shipping/migrations/0002_auto_20190318_1237.py @@ -1,24 +1,29 @@ # Generated by Django 2.1.7 on 2019-03-18 17:37 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('shipping', '0001_initial'), + ("longclaw_shipping", "0001_initial"), ] operations = [ migrations.AddField( - model_name='shippingrate', - name='destination', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='shipping.Address'), + model_name="shippingrate", + name="destination", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="longclaw_shipping.Address", + ), ), migrations.AddField( - model_name='shippingrate', - name='basket_id', + model_name="shippingrate", + name="basket_id", field=models.CharField(blank=True, db_index=True, max_length=32), ), ] diff --git a/longclaw/shipping/migrations/0003_auto_20190322_1429.py b/longclaw/shipping/migrations/0003_auto_20190322_1429.py index 44f019be..1f1e9b3f 100644 --- a/longclaw/shipping/migrations/0003_auto_20190322_1429.py +++ b/longclaw/shipping/migrations/0003_auto_20190322_1429.py @@ -1,32 +1,54 @@ # Generated by Django 2.1.7 on 2019-03-22 19:29 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ('shipping', '0002_auto_20190318_1237'), + ("contenttypes", "0002_remove_content_type_name"), + ("longclaw_shipping", "0002_auto_20190318_1237"), ] operations = [ migrations.CreateModel( - name='ShippingRateProcessor', + name="ShippingRateProcessor", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('countries', models.ManyToManyField(to='shipping.Country')), - ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_shipping.shippingrateprocessor_set+', to='contenttypes.ContentType')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("countries", models.ManyToManyField(to="longclaw_shipping.Country")), + ( + "polymorphic_ctype", + models.ForeignKey( + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="polymorphic_shipping.shippingrateprocessor_set+", + to="contenttypes.ContentType", + ), + ), ], options={ - 'base_manager_name': 'objects', - 'abstract': False, + "base_manager_name": "objects", + "abstract": False, }, ), migrations.AddField( - model_name='shippingrate', - name='processor', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='shipping.ShippingRateProcessor'), + model_name="shippingrate", + name="processor", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="longclaw_shipping.ShippingRateProcessor", + ), ), ] diff --git a/longclaw/shipping/migrations/0004_alter_shippingrateprocessor_polymorphic_ctype.py b/longclaw/shipping/migrations/0004_alter_shippingrateprocessor_polymorphic_ctype.py new file mode 100644 index 00000000..a6f9e3a3 --- /dev/null +++ b/longclaw/shipping/migrations/0004_alter_shippingrateprocessor_polymorphic_ctype.py @@ -0,0 +1,26 @@ +# Generated by Django 4.1.3 on 2022-11-12 11:28 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("contenttypes", "0002_remove_content_type_name"), + ("longclaw_shipping", "0003_auto_20190322_1429"), + ] + + operations = [ + migrations.AlterField( + model_name="shippingrateprocessor", + name="polymorphic_ctype", + field=models.ForeignKey( + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="polymorphic_%(app_label)s.%(class)s_set+", + to="contenttypes.contenttype", + ), + ), + ] diff --git a/longclaw/shipping/migrations/0005_alter_address_options.py b/longclaw/shipping/migrations/0005_alter_address_options.py new file mode 100644 index 00000000..39999a73 --- /dev/null +++ b/longclaw/shipping/migrations/0005_alter_address_options.py @@ -0,0 +1,17 @@ +# Generated by Django 4.1.3 on 2022-11-16 21:42 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("longclaw_shipping", "0004_alter_shippingrateprocessor_polymorphic_ctype"), + ] + + operations = [ + migrations.AlterModelOptions( + name="address", + options={"verbose_name": "Address", "verbose_name_plural": "Addresses"}, + ), + ] diff --git a/longclaw/shipping/models/__init__.py b/longclaw/shipping/models/__init__.py index 0db7149f..4053159f 100644 --- a/longclaw/shipping/models/__init__.py +++ b/longclaw/shipping/models/__init__.py @@ -1,3 +1,3 @@ -from .locations import * -from .processors import * -from .rates import * +from .locations import * # noqa +from .processors import * # noqa +from .rates import * # noqa diff --git a/longclaw/shipping/models/locations.py b/longclaw/shipping/models/locations.py index 86c09b69..f3470608 100644 --- a/longclaw/shipping/models/locations.py +++ b/longclaw/shipping/models/locations.py @@ -1,6 +1,11 @@ from django.db import models +from wagtail import VERSION as WAGTAIL_VERSION + +if WAGTAIL_VERSION >= (3, 0): + from wagtail.admin.panels import FieldPanel +else: + from wagtail.admin.edit_handlers import FieldPanel -from wagtail.admin.edit_handlers import FieldPanel from wagtail.snippets.models import register_snippet @@ -11,20 +16,26 @@ class Address(models.Model): line_2 = models.CharField(max_length=128, blank=True) city = models.CharField(max_length=64) postcode = models.CharField(max_length=10) - country = models.ForeignKey('shipping.Country', blank=True, null=True, on_delete=models.PROTECT) + country = models.ForeignKey( + "longclaw_shipping.Country", blank=True, null=True, on_delete=models.PROTECT + ) panels = [ - FieldPanel('name'), - FieldPanel('line_1'), - FieldPanel('line_2'), - FieldPanel('city'), - FieldPanel('postcode'), - FieldPanel('country') + FieldPanel("name"), + FieldPanel("line_1"), + FieldPanel("line_2"), + FieldPanel("city"), + FieldPanel("postcode"), + FieldPanel("country"), ] def __str__(self): return "{}, {}, {}".format(self.name, self.city, self.country) + class Meta: + verbose_name_plural = "Addresses" + verbose_name = "Address" + class Country(models.Model): """ @@ -42,15 +53,19 @@ class Country(models.Model): country_grabber.py's priority dictionary and run it to regenerate the json """ + iso = models.CharField(max_length=2, primary_key=True) name_official = models.CharField(max_length=128) name = models.CharField(max_length=128) sort_priority = models.PositiveIntegerField(default=0) class Meta: - verbose_name_plural = 'Countries' - ordering = ('-sort_priority', 'name',) + verbose_name_plural = "Countries" + ordering = ( + "-sort_priority", + "name", + ) def __str__(self): - """ Return the display form of the country name""" + """Return the display form of the country name""" return self.name diff --git a/longclaw/shipping/models/processors.py b/longclaw/shipping/models/processors.py index 2f62a45e..19524803 100644 --- a/longclaw/shipping/models/processors.py +++ b/longclaw/shipping/models/processors.py @@ -1,25 +1,28 @@ -import json import hashlib +import json -from django.utils.encoding import force_bytes, force_text from django.core.cache import cache from django.core.serializers.json import DjangoJSONEncoder from django.db import models, transaction -from django.dispatch import receiver -from longclaw.basket.models import BasketItem -from longclaw.basket.signals import basket_modified +# if DJANGO_VERSION < (4, 0): +# from django.utils.encoding import force_text as force_str +# else: +from django.utils.encoding import force_bytes, force_str from polymorphic.models import PolymorphicModel -from wagtail.admin.edit_handlers import FieldPanel + +from longclaw.basket.models import BasketItem from ..serializers.locations import AddressSerializer -from ..signals import address_modified + +# from django import VERSION as DJANGO_VERSION class ShippingRateProcessor(PolymorphicModel): - countries = models.ManyToManyField('shipping.Country') - + countries = models.ManyToManyField("longclaw_shipping.Country") + rates_cache_timeout = 300 + def get_rates(self, settings=None, basket_id=None, destination=None): kwargs = dict(settings=settings, basket_id=basket_id, destination=destination) key = self.get_rates_cache_key(**kwargs) @@ -30,38 +33,38 @@ def get_rates(self, settings=None, basket_id=None, destination=None): if rates is not None: cache.set(key, rates, self.rates_cache_timeout) return rates - + def get_rates_cache_key(self, **kwargs): from longclaw.basket.serializers import BasketItemSerializer - - settings = kwargs['settings'] + + settings = kwargs["settings"] origin = settings.shipping_origin - destination = kwargs['destination'] - basket_id = kwargs['basket_id'] - + destination = kwargs["destination"] + basket_id = kwargs["basket_id"] + items = BasketItem.objects.filter(basket_id=basket_id) serialized_items = BasketItemSerializer(items, many=True) - + serialized_origin = AddressSerializer(origin) or None serialized_destination = AddressSerializer(destination) or None - + data = { "items": serialized_items.data, "origin": serialized_origin.data, "destination": serialized_destination.data, } - + raw_key = json.dumps( data, sort_keys=True, indent=4, - separators=(',', ': '), + separators=(",", ": "), cls=DjangoJSONEncoder, ) - + hashed_key = hashlib.sha1(force_bytes(raw_key)).hexdigest() - - return force_text(hashed_key) - + + return force_str(hashed_key) + def process_rates(self, **kwargs): raise NotImplementedError() diff --git a/longclaw/shipping/models/rates.py b/longclaw/shipping/models/rates.py index 5c8a24e6..8d09ab16 100644 --- a/longclaw/shipping/models/rates.py +++ b/longclaw/shipping/models/rates.py @@ -1,8 +1,13 @@ from django.db import models from django.dispatch import receiver +from wagtail import VERSION as WAGTAIL_VERSION + +if WAGTAIL_VERSION >= (3, 0): + from wagtail.admin.panels import FieldPanel +else: + from wagtail.admin.edit_handlers import FieldPanel from longclaw.basket.signals import basket_modified -from wagtail.admin.edit_handlers import FieldPanel from ..signals import address_modified @@ -12,25 +17,33 @@ class ShippingRate(models.Model): An individual shipping rate. This can be applied to multiple countries. """ + name = models.CharField( max_length=32, unique=True, - help_text="Unique name to refer to this shipping rate by" + help_text="Unique name to refer to this shipping rate by", ) rate = models.DecimalField(max_digits=12, decimal_places=2) carrier = models.CharField(max_length=64) description = models.CharField(max_length=128) - countries = models.ManyToManyField('shipping.Country') + countries = models.ManyToManyField("longclaw_shipping.Country") basket_id = models.CharField(blank=True, db_index=True, max_length=32) - destination = models.ForeignKey('shipping.Address', blank=True, null=True, on_delete=models.PROTECT) - processor = models.ForeignKey('shipping.ShippingRateProcessor', blank=True, null=True, on_delete=models.PROTECT) + destination = models.ForeignKey( + "longclaw_shipping.Address", blank=True, null=True, on_delete=models.PROTECT + ) + processor = models.ForeignKey( + "longclaw_shipping.ShippingRateProcessor", + blank=True, + null=True, + on_delete=models.PROTECT, + ) panels = [ - FieldPanel('name'), - FieldPanel('rate'), - FieldPanel('carrier'), - FieldPanel('description'), - FieldPanel('countries') + FieldPanel("name"), + FieldPanel("rate"), + FieldPanel("carrier"), + FieldPanel("description"), + FieldPanel("countries"), ] def __str__(self): diff --git a/longclaw/shipping/serializers/__init__.py b/longclaw/shipping/serializers/__init__.py index db5e329d..f85b9018 100644 --- a/longclaw/shipping/serializers/__init__.py +++ b/longclaw/shipping/serializers/__init__.py @@ -1,2 +1,2 @@ -from .locations import * -from .rates import * +from .locations import * # noqa +from .rates import * # noqa diff --git a/longclaw/shipping/serializers/locations.py b/longclaw/shipping/serializers/locations.py index 3364b3ed..b19ea5e3 100644 --- a/longclaw/shipping/serializers/locations.py +++ b/longclaw/shipping/serializers/locations.py @@ -2,12 +2,15 @@ from longclaw.shipping.models.locations import Address, Country + class AddressSerializer(serializers.ModelSerializer): country = serializers.PrimaryKeyRelatedField(queryset=Country.objects.all()) + class Meta: model = Address fields = "__all__" + class CountrySerializer(serializers.ModelSerializer): class Meta: model = Country diff --git a/longclaw/shipping/serializers/rates.py b/longclaw/shipping/serializers/rates.py index 89ea3883..3e5d9131 100644 --- a/longclaw/shipping/serializers/rates.py +++ b/longclaw/shipping/serializers/rates.py @@ -2,6 +2,7 @@ from longclaw.shipping.models.rates import ShippingRate + class ShippingRateSerializer(serializers.ModelSerializer): class Meta: model = ShippingRate diff --git a/longclaw/shipping/signals.py b/longclaw/shipping/signals.py index feb97ad3..cd7528a7 100644 --- a/longclaw/shipping/signals.py +++ b/longclaw/shipping/signals.py @@ -1,3 +1,3 @@ import django.dispatch -address_modified = django.dispatch.Signal(providing_args=['instance']) +address_modified = django.dispatch.Signal("instance") diff --git a/longclaw/shipping/templatetags/longclawshipping_tags.py b/longclaw/shipping/templatetags/longclawshipping_tags.py index d9c9eb52..ea536187 100644 --- a/longclaw/shipping/templatetags/longclawshipping_tags.py +++ b/longclaw/shipping/templatetags/longclawshipping_tags.py @@ -1,15 +1,15 @@ from django import template -from longclaw.shipping.utils import get_shipping_cost -from longclaw.configuration.models import Configuration +from longclaw.configuration.models import Configuration +from longclaw.shipping.utils import get_shipping_cost register = template.Library() + @register.simple_tag(takes_context=True) def shipping_rate(context, **kwargs): - """Return the shipping rate for a country & shipping option name. - """ + """Return the shipping rate for a country & shipping option name.""" settings = Configuration.for_site(context["request"].site) - code = kwargs.get('code', None) - name = kwargs.get('name', None) + code = kwargs.get("code", None) + name = kwargs.get("name", None) return get_shipping_cost(settings, code, name) diff --git a/longclaw/shipping/urls.py b/longclaw/shipping/urls.py index 4c14139c..4a0974b5 100644 --- a/longclaw/shipping/urls.py +++ b/longclaw/shipping/urls.py @@ -1,35 +1,38 @@ -from django.conf.urls import url -from longclaw.shipping import api +from django.urls import path + from longclaw.settings import API_URL_PREFIX +from longclaw.shipping import api -address_list = api.AddressViewSet.as_view({ - 'get': 'list', - 'post': 'create' -}) -address_detail = api.AddressViewSet.as_view({ - 'get': 'retrieve', - 'put': 'update', - 'patch': 'partial_update', - 'delete': 'destroy' -}) +address_list = api.AddressViewSet.as_view({"get": "list", "post": "create"}) +address_detail = api.AddressViewSet.as_view( + {"get": "retrieve", "put": "update", "patch": "partial_update", "delete": "destroy"} +) urlpatterns = [ - url(API_URL_PREFIX + r'addresses/$', - address_list, - name='longclaw_address_list'), - url(API_URL_PREFIX + r'addresses/(?P[0-9]+)/$', + path(API_URL_PREFIX + "addresses/", address_list, name="longclaw_address_list"), + path( + API_URL_PREFIX + "addresses//", address_detail, - name='longclaw_address_detail'), - url(API_URL_PREFIX + r'shipping/cost/$', + name="longclaw_address_detail", + ), + path( + API_URL_PREFIX + "shipping/cost/", api.shipping_cost, - name='longclaw_shipping_cost'), - url(API_URL_PREFIX + r'shipping/countries/$', + name="longclaw_shipping_cost", + ), + path( + API_URL_PREFIX + "shipping/countries/", api.shipping_countries, - name='longclaw_shipping_countries'), - url(API_URL_PREFIX + r'shipping/countries/(?P[a-zA-Z]+)/$', + name="longclaw_shipping_countries", + ), + path( + API_URL_PREFIX + "shipping/countries//", api.shipping_options, - name='longclaw_shipping_options'), - url(API_URL_PREFIX + r'shipping/options/$', + name="longclaw_shipping_options", + ), + path( + API_URL_PREFIX + "shipping/options/", api.shipping_options, - name='longclaw_applicable_shipping_rate_list') + name="longclaw_applicable_shipping_rate_list", + ), ] diff --git a/longclaw/shipping/utils.py b/longclaw/shipping/utils.py index 7c16dcd3..b0c19475 100644 --- a/longclaw/shipping/utils.py +++ b/longclaw/shipping/utils.py @@ -15,45 +15,49 @@ class InvalidShippingDestination(Exception): pass -def get_shipping_cost(settings, country_code=None, name=None, basket_id=None, destination=None): - """Return the shipping cost for a given country code and shipping option (shipping rate name) - """ +def get_shipping_cost( + settings, country_code=None, name=None, basket_id=None, destination=None +): + """Return the shipping cost for a given country code and shipping option (shipping rate name)""" if not country_code and destination: country_code = destination.country.pk - + shipping_rate = None invalid_country = False if settings.default_shipping_enabled: shipping_rate = { "rate": settings.default_shipping_rate, "description": "Standard shipping to rest of world", - "carrier": settings.default_shipping_carrier + "carrier": settings.default_shipping_carrier, } elif not country_code: invalid_country = True if country_code: - qrs = models.ShippingRate.objects.filter(countries__in=[country_code], name=name) + qrs = models.ShippingRate.objects.filter( + countries__in=[country_code], name=name + ) count = qrs.count() if count == 1: shipping_rate_qrs = qrs[0] shipping_rate = { "rate": shipping_rate_qrs.rate, "description": shipping_rate_qrs.description, - "carrier": shipping_rate_qrs.carrier} - + "carrier": shipping_rate_qrs.carrier, + } + if basket_id or destination: q = Q() - + if destination and basket_id: q.add(Q(destination=destination, basket_id=basket_id), Q.OR) - + if destination: - q.add(Q(destination=destination, basket_id=''), Q.OR) - + q.add(Q(destination=destination, basket_id=""), Q.OR) + if basket_id: q.add(Q(destination=None, basket_id=basket_id), Q.OR) - + qrs = models.ShippingRate.objects.filter(name=name).filter(q) count = qrs.count() if count == 1: @@ -61,11 +65,12 @@ def get_shipping_cost(settings, country_code=None, name=None, basket_id=None, de shipping_rate = { "rate": shipping_rate_qrs.rate, "description": shipping_rate_qrs.description, - "carrier": shipping_rate_qrs.carrier} - + "carrier": shipping_rate_qrs.carrier, + } + if not shipping_rate: if invalid_country: raise InvalidShippingCountry raise InvalidShippingRate() - + return shipping_rate diff --git a/longclaw/shipping/wagtail_hooks.py b/longclaw/shipping/wagtail_hooks.py index 0a48d10e..c4001572 100644 --- a/longclaw/shipping/wagtail_hooks.py +++ b/longclaw/shipping/wagtail_hooks.py @@ -1,17 +1,29 @@ -from wagtail.contrib.modeladmin.options import ( - ModelAdmin, modeladmin_register -) +from django.utils.safestring import mark_safe +from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register + from longclaw.shipping.models import ShippingRate class ShippingRateModelAdmin(ModelAdmin): model = ShippingRate - menu_label = 'Shipping' + menu_label = "Shipping" menu_order = 200 - menu_icon = 'site' + menu_icon = "site" add_to_settings_menu = False exclude_from_explorer = True - list_display = ('name', 'rate', 'carrier', 'description') + list_display = ( + "name", + "rate", + "carrier", + "description", + "shipping_country", + ) # TODO: Add country field and filtering/searching + + def shipping_country(self, obj): + countries = " | ".join([country.name for country in obj.countries.all()]) + return mark_safe(countries) + + shipping_country.short_description = "Countries" modeladmin_register(ShippingRateModelAdmin) diff --git a/longclaw/stats/stats.py b/longclaw/stats/stats.py index fb4c673c..38af1901 100644 --- a/longclaw/stats/stats.py +++ b/longclaw/stats/stats.py @@ -1,18 +1,22 @@ """ Various stats/analysis calculations """ -import itertools import calendar +import itertools from datetime import datetime -from django.db.models import Q, Sum, F + +from django.db.models import F, Q, Sum + from longclaw.orders.models import Order, OrderItem def current_month(): now = datetime.now() n_days = calendar.monthrange(now.year, now.month)[1] - month_start = datetime.strptime('{}{}{}'.format(now.year, now.month, 1), '%Y%m%d') - month_end = datetime.strptime('{}{}{}'.format(now.year,now.month, n_days), '%Y%m%d') + month_start = datetime.strptime("{}{}{}".format(now.year, now.month, 1), "%Y%m%d") + month_end = datetime.strptime( + "{}{}{}".format(now.year, now.month, n_days), "%Y%m%d" + ) return month_start, month_end @@ -29,20 +33,22 @@ def sales_for_time_period(from_date, to_date): def daily_sales(from_date, to_date): sales = sales_for_time_period(from_date, to_date) - grouped = itertools.groupby(sales, lambda order: order.payment_date.strftime("%Y-%m-%d")) + grouped = itertools.groupby( + sales, lambda order: order.payment_date.strftime("%Y-%m-%d") + ) return grouped + def sales_by_product(from_date, to_date): - sales = OrderItem.objects.filter( - Q(order__payment_date__lte=to_date) & Q(order__payment_date__gte=from_date) - ).exclude( - order__status=Order.CANCELLED - ).annotate( - title=F('product__product__title') - ).values( - 'title' - ).annotate( - quantity=Sum('quantity') - ).order_by('-quantity') + sales = ( + OrderItem.objects.filter( + Q(order__payment_date__lte=to_date) & Q(order__payment_date__gte=from_date) + ) + .exclude(order__status=Order.CANCELLED) + .annotate(title=F("product__product__title")) + .values("title") + .annotate(quantity=Sum("quantity")) + .order_by("-quantity") + ) return sales diff --git a/longclaw/stats/templates/stats/longclaw_stats.html b/longclaw/stats/templates/stats/longclaw_stats.html new file mode 100644 index 00000000..8c12ff25 --- /dev/null +++ b/longclaw/stats/templates/stats/longclaw_stats.html @@ -0,0 +1,73 @@ +{% extends "wagtailadmin/base.html" %} +{% load wagtailadmin_tags %} +{% block titletag %}Longclaw Stats{% endblock %} + +{% block extra_css %} + {{ block.super }} + +{% endblock %} + +{% block content %} + {% include "wagtailadmin/shared/header.html" with title=title icon=icon %} + +
+

{{ graphs_title }}

+ {% for panel in graphs %} + {{ panel.render_html }} + {% endfor %} +
+ +
+

{{ summary_title }}

+
    + {% for panel in summary %} + {{ panel.render_html }} + {% endfor %} +
+
+{% endblock %} diff --git a/longclaw/stats/views.py b/longclaw/stats/views.py new file mode 100644 index 00000000..7ea08d51 --- /dev/null +++ b/longclaw/stats/views.py @@ -0,0 +1,108 @@ +import datetime + +from django.shortcuts import render +from wagtail.admin.site_summary import SummaryItem + +from longclaw.configuration.models import Configuration +from longclaw.orders.models import Order +from longclaw.stats import stats +from longclaw.utils import ProductVariant, maybe_get_product_model + + +class LongclawStatsPanel(SummaryItem): + order = 110 + + template_name = "stats/stats_panel.html" + + def get_context_data(self, parent_context): + month_start, month_end = stats.current_month() + daily_sales = stats.daily_sales(month_start, month_end) + labels = [ + (month_start + datetime.timedelta(days=x)).strftime("%Y-%m-%d") + for x in range(0, datetime.datetime.now().day) + ] + daily_income = [0] * len(labels) + for k, order_group in daily_sales: + i = labels.index(k) + daily_income[i] = float(sum(order.total for order in order_group)) + + popular_products = stats.sales_by_product(month_start, month_end)[:5] + return { + "daily_income": daily_income, + "labels": labels, + "product_labels": list(popular_products.values_list("title", flat=True)), + "sales_volume": list(popular_products.values_list("quantity", flat=True)), + } + + +class LongclawSummaryItem(SummaryItem): + order = 10 + + template_name = "stats/summary_item.html" + + def get_context_data(self, parent_context): + return {"total": 0, "text": "", "url": "", "icon": "icon-doc-empty-inverse"} + + +class OutstandingOrders(LongclawSummaryItem): + order = 10 + + def get_context_data(self, parent_context): + orders = Order.objects.filter(status=Order.SUBMITTED) + return { + "total": orders.count(), + "text": "Outstanding Orders", + "url": "/admin/longclaw_orders/order/", + "icon": "icon-warning", + } + + +class ProductCount(LongclawSummaryItem): + order = 20 + + def get_context_data(self, parent_context): + product_model = maybe_get_product_model() + if product_model: + count = product_model.objects.all().count() + else: + count = ProductVariant.objects.all().count() + return {"total": count, "text": "Product", "url": "", "icon": "icon-list-ul"} + + +class MonthlySales(LongclawSummaryItem): + order = 30 + + def get_context_data(self, parent_context): + settings = Configuration.for_site(self.request.site) + sales = stats.sales_for_time_period(*stats.current_month()) + return { + "total": "{}{}".format( + settings.currency_html_code, sum(order.total for order in sales) + ), + "text": "In sales this month", + "url": "/admin/longclaw_orders/order/", + "icon": "icon-tick", + } + + +def longclaw_stats_view(request): + graphs = [ + LongclawStatsPanel(request), + ] + summary = [ + OutstandingOrders(request), + ProductCount(request), + MonthlySales(request), + ] + return render( + request, + "stats/longclaw_stats.html", + { + "title": "Longclaw Dashboard", + "icon": "table", + "graphs_title": "Performance", + "graphs": graphs, + "summary_title": "Summary", + "summary": summary, + }, + ) diff --git a/longclaw/stats/wagtail_hooks.py b/longclaw/stats/wagtail_hooks.py index b0629cf6..06443982 100644 --- a/longclaw/stats/wagtail_hooks.py +++ b/longclaw/stats/wagtail_hooks.py @@ -1,98 +1,33 @@ -import datetime -from wagtail.core import hooks -from wagtail.admin.site_summary import SummaryItem -from longclaw.orders.models import Order -from longclaw.stats import stats -from longclaw.configuration.models import Configuration -from longclaw.utils import ProductVariant, maybe_get_product_model +from django.urls import path, reverse +from wagtail import VERSION as WAGTAIL_VERSION +from wagtail.admin.menu import MenuItem, SubmenuMenuItem +from wagtail.contrib.modeladmin.menus import ( # TODO: seeing console warnings about this being deprecated v5.0 + SubMenu, +) +from longclaw.stats import views -class LongclawSummaryItem(SummaryItem): - order = 10 - template = 'stats/summary_item.html' +if WAGTAIL_VERSION >= (3, 0): + from wagtail import hooks +else: + from wagtail.core import hooks - def get_context(self): - return { - 'total': 0, - 'text': '', - 'url': '', - 'icon': 'icon-doc-empty-inverse' - } -class OutstandingOrders(LongclawSummaryItem): - order = 10 - def get_context(self): - orders = Order.objects.filter(status=Order.SUBMITTED) - return { - 'total': orders.count(), - 'text': 'Outstanding Orders', - 'url': '/admin/orders/order/', - 'icon': 'icon-warning' - } +@hooks.register("register_admin_urls") +def register_longclaw_stats_url(): -class ProductCount(LongclawSummaryItem): - order = 20 - def get_context(self): - product_model = maybe_get_product_model() - if product_model: - count = product_model.objects.all().count() - else: - count = ProductVariant.objects.all().count() - return { - 'total': count, - 'text': 'Product', - 'url': '', - 'icon': 'icon-list-ul' - } + return [ + path("longclaw_stats/", views.longclaw_stats_view, name="longclaw_stats"), + ] -class MonthlySales(LongclawSummaryItem): - order = 30 - def get_context(self): - settings = Configuration.for_site(self.request.site) - sales = stats.sales_for_time_period(*stats.current_month()) - return { - 'total': "{}{}".format(settings.currency_html_code, - sum(order.total for order in sales)), - 'text': 'In sales this month', - 'url': '/admin/orders/order/', - 'icon': 'icon-tick' - } -class LongclawStatsPanel(SummaryItem): - order = 110 - template = 'stats/stats_panel.html' - def get_context(self): - month_start, month_end = stats.current_month() - daily_sales = stats.daily_sales(month_start, month_end) - labels = [(month_start + datetime.timedelta(days=x)).strftime('%Y-%m-%d') - for x in range(0, datetime.datetime.now().day)] - daily_income = [0] * len(labels) - for k, order_group in daily_sales: - i = labels.index(k) - daily_income[i] = float(sum(order.total for order in order_group)) +@hooks.register("register_admin_menu_item") +def register_longclaw_stats_menu_item(): - popular_products = stats.sales_by_product(month_start, month_end)[:5] - return { - "daily_income": daily_income, - "labels": labels, - "product_labels": list(popular_products.values_list('title', flat=True)), - "sales_volume": list(popular_products.values_list('quantity', flat=True)) - } + menu_items = [ + MenuItem("Dashboard", reverse("longclaw_stats"), icon_name="table", order=10000) + ] - - - -@hooks.register('construct_homepage_summary_items') -def add_longclaw_summary_items(request, items): - - # We are going to replace everything with our own items - items[:] = [] - items.extend([ - OutstandingOrders(request), - ProductCount(request), - MonthlySales(request) - ]) - -@hooks.register('construct_homepage_panels') -def add_stats_panel(request, panels): - return panels.append(LongclawStatsPanel(request)) + return SubmenuMenuItem( + "Longclaw", SubMenu(menu_items), icon_name="table", order=10000 + ) diff --git a/longclaw/test/__init__.py b/longclaw/test/__init__.py new file mode 100644 index 00000000..23b11e18 --- /dev/null +++ b/longclaw/test/__init__.py @@ -0,0 +1 @@ +default_apps_config = "longclaw.test.apps.LongclawTestConfig" diff --git a/longclaw/test/apps.py b/longclaw/test/apps.py new file mode 100644 index 00000000..a02676ac --- /dev/null +++ b/longclaw/test/apps.py @@ -0,0 +1,8 @@ +from django.apps import AppConfig + + +class LonglawTestConfig(AppConfig): + label = "longclaw_test" + name = "longclaw.test" + verbose_name = "Longclaw Test" + default_auto_field = "django.db.models.AutoField" diff --git a/longclaw/test/migrations/0001_initial.py b/longclaw/test/migrations/0001_initial.py new file mode 100644 index 00000000..63cb46b9 --- /dev/null +++ b/longclaw/test/migrations/0001_initial.py @@ -0,0 +1,176 @@ +# Generated by Django 4.0.8 on 2022-11-12 22:13 + +import django.db.models.deletion +import modelcluster.fields +import wagtail.core.fields +from django.db import migrations, models + + +def create_homepage(apps, schema_editor): + # Get models + ContentType = apps.get_model("contenttypes.ContentType") + Page = apps.get_model("wagtailcore.Page") + Site = apps.get_model("wagtailcore.Site") + HomePage = apps.get_model("longclaw_test.HomePage") + + # Delete the default homepage + # If migration is run multiple times, it may have already been deleted + Page.objects.filter(id=2).delete() + + # Create content type for homepage model + homepage_content_type, __ = ContentType.objects.get_or_create( + model="homepage", app_label="longclaw_test" + ) + + # Create a new homepage + homepage = HomePage.objects.create( + title="Home", + draft_title="Home", + slug="home", + content_type=homepage_content_type, + path="00010001", + depth=2, + numchild=0, + url_path="/home/", + ) + + # Create a site with the new homepage set as the root + Site.objects.create(hostname="localhost", root_page=homepage, is_default_site=True) + + +def remove_homepage(apps, schema_editor): + # Get models + ContentType = apps.get_model("contenttypes.ContentType") + HomePage = apps.get_model("longclaw_test.HomePage") + + # Delete the default homepage + # Page and Site objects CASCADE + HomePage.objects.filter(slug="home", depth=2).delete() + + # Delete content type for homepage model + ContentType.objects.filter(model="homepage", app_label="longclaw_test").delete() + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("wagtailcore", "0040_page_draft_title"), + ("longclaw_shipping", "0004_alter_shippingrateprocessor_polymorphic_ctype"), + ] + + operations = [ + migrations.CreateModel( + name="HomePage", + fields=[ + ( + "page_ptr", + models.OneToOneField( + on_delete=models.CASCADE, + parent_link=True, + auto_created=True, + primary_key=True, + serialize=False, + to="wagtailcore.Page", + ), + ), + ], + options={ + "abstract": False, + }, + bases=("wagtailcore.page",), + ), + migrations.CreateModel( + name="Product", + fields=[ + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.page", + ), + ), + ("description", wagtail.core.fields.RichTextField()), + ], + options={ + "abstract": False, + }, + bases=("wagtailcore.page",), + ), + migrations.CreateModel( + name="ProductIndex", + fields=[ + ( + "page_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="wagtailcore.page", + ), + ), + ], + options={ + "abstract": False, + }, + bases=("wagtailcore.page",), + ), + migrations.CreateModel( + name="TrivialShippingRateProcessor", + fields=[ + ( + "shippingrateprocessor_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="longclaw_shipping.shippingrateprocessor", + ), + ), + ], + options={ + "abstract": False, + "base_manager_name": "objects", + }, + bases=("longclaw_shipping.shippingrateprocessor",), + ), + migrations.CreateModel( + name="ProductVariant", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("base_price", models.DecimalField(decimal_places=2, max_digits=12)), + ("ref", models.CharField(max_length=32)), + ("stock", models.IntegerField(default=0)), + ("description", wagtail.core.fields.RichTextField()), + ( + "product", + modelcluster.fields.ParentalKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="variants", + to="longclaw_test.product", + ), + ), + ], + options={ + "abstract": False, + }, + ), + migrations.RunPython(create_homepage, remove_homepage), + ] diff --git a/longclaw/project_template/home/__init__.py b/longclaw/test/migrations/__init__.py similarity index 100% rename from longclaw/project_template/home/__init__.py rename to longclaw/test/migrations/__init__.py diff --git a/longclaw/test/models.py b/longclaw/test/models.py new file mode 100644 index 00000000..f6a3f43b --- /dev/null +++ b/longclaw/test/models.py @@ -0,0 +1,99 @@ +import hashlib + +# if DJANGO_VERSION < (4, 0): +# from django.utils.encoding import force_text as force_str +# else: +from django.utils.encoding import force_bytes, force_str + +# from django import VERSION as DJANGO_VERSION +from modelcluster.fields import ParentalKey +from wagtail import VERSION as WAGTAIL_VERSION + +if WAGTAIL_VERSION >= (3, 0): + from wagtail.admin.panels import FieldPanel, InlinePanel + from wagtail.fields import RichTextField + from wagtail.models import Page +else: + from wagtail.admin.edit_handlers import FieldPanel, InlinePanel + from wagtail.core.fields import RichTextField + from wagtail.core.models import Page + +from longclaw.basket.models import BasketItem +from longclaw.products.models import ProductBase, ProductVariantBase +from longclaw.shipping.models import ShippingRate, ShippingRateProcessor + + +class HomePage(Page): + pass + + +class ProductIndex(Page): + """Index page for all products""" + + subpage_types = ("Product", "ProductIndex") + + +class Product(ProductBase): + parent_page_types = [ProductIndex] + description = RichTextField() + content_panels = ProductBase.content_panels + [ + FieldPanel("description"), + InlinePanel("variants"), + ] + + +class ProductVariant(ProductVariantBase): + """Basic product variant for testing""" + + product = ParentalKey(Product, related_name="variants") + description = RichTextField() + + @ProductVariantBase.price.getter + def price(self): + """Make the price dynamic to check that longclaw works with ``get_price``""" + return self.base_price * 10 + + +class TrivialShippingRateProcessor(ShippingRateProcessor): + def process_rates(self, **kwargs): + destination = kwargs["destination"] + basket_id = kwargs["basket_id"] + + item_count = BasketItem.objects.filter(basket_id=basket_id).count() + + rates = [] + + quotes = [] + + if 0 < item_count: + quotes.append((item_count * 2, "turtle")) + + if 1 < item_count: + quotes.append((item_count * 4, "rabbit")) + + if 2 < item_count: + quotes.append((item_count * 16, "cheetah")) + + for amount, speed in quotes: + name = self.get_processed_rate_name(destination, basket_id, speed) + lookups = dict(name=name) + values = dict( + rate=amount, + carrier="TrivialShippingRateProcessor", + description="Delivered with {} speed".format(speed), + basket_id=basket_id, + destination=destination, + processor=self, + ) + + rate = ShippingRate.objects.update_or_create(defaults=values, **lookups) + rates.append(rate) + + return rates + + def get_processed_rate_name(self, destination, basket_id, speed): + name_long = "TrivialShippingRateProcessor-{}-{}-{}".format( + destination.pk, basket_id, speed + ) + name = hashlib.md5(force_bytes(name_long)).hexdigest() + return force_str(name) diff --git a/longclaw/test/settings.py b/longclaw/test/settings.py new file mode 100644 index 00000000..abd9f251 --- /dev/null +++ b/longclaw/test/settings.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 +from __future__ import absolute_import, unicode_literals + +import os + +from wagtail import VERSION as WAGTAIL_VERSION + +# import sys + + +PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +BASE_DIR = os.path.dirname(PROJECT_DIR) + +DEBUG = True + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk" + +# Database +# https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#databases + +# if "test" in sys.argv: +# sys.stdout.write("*** Using in-memory sqlite database for tests ***\n") +# DATABASES = { +# "default": { +# "ENGINE": "django.db.backends.sqlite3", +# "NAME": ":memory:", +# }, +# } +# else: +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": os.path.join(BASE_DIR, "test_longclaw_db.sqlite3"), + }, +} + +ROOT_URLCONF = "longclaw.test.urls" + +WAGTAILSEARCH_BACKENDS = { + "default": { + "BACKEND": "wagtail.search.backends.database", + } +} + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sites", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "wagtail.contrib.forms", + "wagtail.contrib.redirects", + "wagtail.contrib.styleguide", + "wagtail.embeds", + "wagtail.sites", + "wagtail.users", + "wagtail.snippets", + "wagtail.documents", + "wagtail.images", + "wagtail.search", + "wagtail.admin", + "wagtail" if WAGTAIL_VERSION >= (3, 0) else "wagtail.core", + "wagtail.contrib.modeladmin", + "wagtail.contrib.settings", + "modelcluster", + "taggit", + "rest_framework", + "django_extensions", + "longclaw.core", + "longclaw.configuration", + "longclaw.shipping", + "longclaw.products", + "longclaw.orders", + "longclaw.checkout", + "longclaw.basket", + "longclaw.stats", + "longclaw.test", + "longclaw.contrib.productrequests", +] + +SITE_ID = 1 + +MIDDLEWARE = [ + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "wagtail.contrib.legacy.sitemiddleware.SiteMiddleware", + "wagtail.contrib.redirects.middleware.RedirectMiddleware", +] + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [ + os.path.join(os.path.dirname(__file__), "templates"), + ], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + "longclaw.configuration.context_processors.currency", + ], + }, + }, +] + +STATIC_URL = "/static/" + +PRODUCT_VARIANT_MODEL = "longclaw_test.ProductVariant" + +# DEFAULT_AUTO_FIELD = "django.db.models.AutoField" + +WAGTAILADMIN_BASE_URL = "http://localhost:8000/admin" + +WAGTAIL_SITE_NAME = "Longclaw" + +# Internationalization +# https://docs.djangoproject.com/en/{{ docs_version }}/topics/i18n/ + +LANGUAGE_CODE = "en-us" + +TIME_ZONE = "UTC" + +USE_I18N = True + +USE_L10N = True + +# USE_TZ = True diff --git a/longclaw/checkout/models.py b/longclaw/test/static/css/longclaw_test.css similarity index 100% rename from longclaw/checkout/models.py rename to longclaw/test/static/css/longclaw_test.css diff --git a/longclaw/project_template/project_name/__init__.py b/longclaw/test/static/js/longclaw_test.js similarity index 100% rename from longclaw/project_template/project_name/__init__.py rename to longclaw/test/static/js/longclaw_test.js diff --git a/longclaw/test/templates/404.html b/longclaw/test/templates/404.html new file mode 100644 index 00000000..3a5500e6 --- /dev/null +++ b/longclaw/test/templates/404.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} + +{% block body_class %}template-404{% endblock %} + +{% block content %} +

Page not found

+ +

Sorry, this page could not be found.

+{% endblock %} diff --git a/longclaw/project_template/project_name/templates/500.html b/longclaw/test/templates/500.html similarity index 100% rename from longclaw/project_template/project_name/templates/500.html rename to longclaw/test/templates/500.html diff --git a/longclaw/test/templates/base.html b/longclaw/test/templates/base.html new file mode 100644 index 00000000..a0da107a --- /dev/null +++ b/longclaw/test/templates/base.html @@ -0,0 +1,98 @@ +{% load static wagtailcore_tags wagtailuserbar %} + + + + + + + + {% block title %} + {% if page.seo_title %}{{ page.seo_title }}{% else %}{{ page.title }}{% endif %} + {% endblock %} + {% block title_suffix %} + {% wagtail_site as current_site %} + {% if current_site and current_site.site_name %}- {{ current_site.site_name }}{% endif %} + {% endblock %} + + + + + {# Force all links in the live preview panel to be opened in a new tab #} + {% if request.in_preview_panel %} + + {% endif %} + + {% comment %} Codyhouse CDN {% endcomment %} + + + + {# Global stylesheets #} + + + {% block extra_css %} + {# Override this in templates to add extra stylesheets #} + {% endblock %} + + + + {% wagtailuserbar %} + +
+
+ +
+
+ +
+ {% block breadcrumbs %} +
+
    +
  • Home
  • + {% if not page.url == "/" %} +
  • {{ page.title }}
  • + {% endif %} + {% block breadcrumbs_extra %} + {% endblock %} +
+
+ + {% endblock %} +
+ {% block content %}{% endblock %} +
+
+ + +
+
+ Why not add a footer +
+
+ + {% comment %} Codyhouse CDN {% endcomment %} + + + {# Global javascript #} + + + {% block extra_js %} + {# Override this in templates to add extra javascript #} + {% endblock %} + + + diff --git a/longclaw/test/templates/basket/basket.html b/longclaw/test/templates/basket/basket.html new file mode 100644 index 00000000..6d5a3604 --- /dev/null +++ b/longclaw/test/templates/basket/basket.html @@ -0,0 +1,67 @@ +{% extends "base.html" %} +{% load wagtailcore_tags wagtailimages_tags %} + +{% block breadcrumbs_extra %} +
  • →Basket
  • +{% endblock %} + +{% block content %} +

    Your Basket

    + + + + + + + + + + + {% for item in basket %} + + + + + + + {% empty %} + + + + {% endfor %} + {% if basket.count > 0 %} + + + + + + + {% endif %} + +
    QuantityPrice
    {% image item.variant.product.first_image.image max-75x75 %}{{item.variant.product.title}} - {{item.variant.ref}}{{item.quantity}}{{item.total}}

    There is nothing in your basket!

    + + + {{total_price}}
    + + + Checkout
    + + +{% endblock %} diff --git a/longclaw/project_template/project_name/templates/checkout/checkout.html b/longclaw/test/templates/checkout/checkout.html similarity index 61% rename from longclaw/project_template/project_name/templates/checkout/checkout.html rename to longclaw/test/templates/checkout/checkout.html index ca47660e..4005b11a 100644 --- a/longclaw/project_template/project_name/templates/checkout/checkout.html +++ b/longclaw/test/templates/checkout/checkout.html @@ -1,8 +1,12 @@ -{% templatetag openblock %} extends "base.html" {% templatetag closeblock %} -{% templatetag openblock %} load longclawcheckout_tags longclawcore_tags {% templatetag closeblock %} +{% extends "base.html" %} +{% load longclawcheckout_tags longclawcore_tags %} -{% templatetag openblock %} block content {% templatetag closeblock %} -{% templatetag opencomment %} +{% block breadcrumbs_extra %} +
  • →Checkout
  • +{% endblock %} + +{% block content %} +{% comment %} {# `checkout_form`, `shipping_form`, `billing_form`, `basket` and `total_price` are the context variables available for you to build up your checkout page. `checkout_form` includes the `different_billing_address` checkbox which you can use to @@ -18,26 +22,26 @@ `basket` is a queryset of `BasketItem` for the current customer. `total_price` is the total cost of all items in the basket. -{% templatetag closecomment %} -{% templatetag openblock %} endblock content {% templatetag closeblock %} +#} {% endcomment %} +{% endblock content %} -{% templatetag openblock %} block extra_js {% templatetag closeblock %} +{% block extra_js %} -{% templatetag opencomment %} +{% comment %} {# Load any client javascript provided by the payment gateway. This will give a list of