This is my implementation of the test described here.
To run this app you'll need:
- Python 3.5+ (It should work with older Python 3 version).
- PIP
- VirtualEnv
- PostgreSQL
There is two easy ways to run and test this app.
- Clone this repository.
- Create a virtualenv that uses
python3.5
binary. e.g.virtualenv -p /usr/bin/python3.5 venv
- Install requirements:
pip install -r requirements-local.txt
- Create a database for the app.
- Create a
.env
file. Uselocal.env
as a example. - Export
.env
:export $(cat .env | xargs)
- Run:
python work-at-olist/manage.py runserver
- Clone this repository.
- Install
heroku-cli
. - Create the heroku app and deploy it.
To test, just run manage.py test
. e.g.
python work-at-olist/manage.py channels
To import a CSV file containing categories, run manage.py importcategories channel csv
where channel
is the name of the target channel to import and csv
is the path to the CSV file. e.g.
python work-at-olist/manage.py importcategories Amazon ~/amazon.csv
About the development environment, I'm developing this project using a PC, running Ubuntu GNOME 16.10. I have been writing these text and code almost entirely in VS Code, with minors editions in Vim. I choose it over PyCharm, Atom and others because I saw this project as a oportunity to test VS Code. I have no regrests so far. Also, there are some tools I'm using to help linting and formating the code: pylint and yapf.
To develop this app I added some extra dependencies:
- django-mptt: implementation of Modified Preorder Tree Traversal models.
- drf-nested-routers: routers and fields to create nested resources in the Django REST Framework.
- djangorestframework-recursive: recursive Serializers for Django REST Framework.
- django-rest-swagger: Swagger documentation generator for Django REST Framework.
- whitenoise: static file serving for WSGI applications.
I used django-mptt to solve the problem of storing hierarchical data on a Relational Database. I came across some other options, but I choose MPTT because of its good query performance. However, it has a drawback, the insertion cost. Anyhow, this isn't a big problem in this case, categories don't change all the time. Also, I could have used PostgreSQL's arrays or ltree, but this would tie the app to PostgreSQL. And, the drf-nested-routers boosts DRF routing allowing nested routes, so /api/v1/channels/{ref}/categories/{ref}/
turned possible. No need to comment about the other ones, they're are very straightforward.
About the app tools, the importcategories
command is silent, following the Unix philosophy: No news is good news. And, when developing the API, I tried to follow best practices and the idea of simplicity.
This app provides a public API with channels
and categories
endpoints. The API is read-only, i.e. it provides only list and retrieve features.
You can check all endpoints and test them accessing /api-docs/
when running the app.
GET /api/v1/channels/
- limit: maximum number of items to retrieve. Optional.
- offset: number of items to skip. Optional.
- search: term to search in
name
andreference
fields. Optional. - ordering: field that is used to order the items. Optional. Default:
reference
.
{
"count": ITEMS_COUNT,
"next": "NEXT_PAGE_URL",
"previous": "PREVIOUS_PAGE_URL",
"results": [
{
"url": "CHANNEL_URL+REFERENCE",
"name": "NAME"
},
...
]
}
GET /api/v1/channels/{reference}/
- reference: channel's reference.
{
"url": "CHANNEL_URL+REFERENCE",
"name": "NAME"
}
GET /api/v1/channels/{ch_reference}/categories/
- ch_reference: channel's reference.
- limit: maximum number of items to retrieve. Optional.
- offset: number of items to skip. Optional.
- search: term to search in
name
andreference
fields. Optional. - ordering: field used to order the items. Optional. Default:
reference
.
{
"count": ITEMS_COUNT,
"next": "NEXT_PAGE_URL",
"previous": "PREVIOUS_PAGE_URL",
"results": [
{
"url": "CATEGORY_URL+REFERENCE",
"name": "NAME",
"channel": "CHANNEL_URL+REFERENCE",
"parent": "CATEGORY_URL+REFERENCE",
"children": [
"CATEGORY_URL+REFERENCE",
...
]
},
...
]
}
GET /api/v1/channels/{ch_reference}/categories/{reference}/
- ch_reference: channel's reference.
- reference: category's reference.
{
"url": "CATEGORY_URL+REFERENCE",
"name": "NAME",
"channel": "CHANNEL_URL+REFERENCE",
"parent": "CATEGORY_URL+REFERENCE",
"children": [
"CATEGORY_URL+REFERENCE",
...
]
}
GET /api/v1/channels/{ch_reference}/categories/{reference}/relatives/
- ch_reference: channel's reference.
- reference: category's reference.
{
"url": "CATEGORY_URL+REFERENCE",
"name": "NAME",
"channel": "CHANNEL_URL+REFERENCE",
"parent": {
"url": "CATEGORY_URL+REFERENCE",
"name": "NAME",
"parent": {...}
},
"children": [
{
"url": "CATEGORY_URL+REFERENCE",
"name": "NAME",
"children": [...]
},
...
]
}
GET /api/v1/categories/
- limit: maximum number of items to retrieve. Optional.
- offset: number of items to skip. Optional.
- search: term to search in
name
andreference
fields. Optional. - ordering: field used to order the items. Optional. Default:
reference
.
{
"count": ITEMS_COUNT,
"next": "NEXT_PAGE_URL",
"previous": "PREVIOUS_PAGE_URL",
"results": [
{
"url": "CATEGORY_URL+REFERENCE",
"name": "NAME",
"channel": "CHANNEL_URL+REFERENCE",
"parent": "CATEGORY_URL+REFERENCE",
"children": [
"CATEGORY_URL+REFERENCE",
...
]
},
...
]
}
GET /api/v1/categories/{reference}/
- reference: category's reference.
{
"url": "CATEGORY_URL+REFERENCE",
"name": "NAME",
"channel": "CHANNEL_URL+REFERENCE",
"parent": "CATEGORY_URL+REFERENCE",
"children": [
"CATEGORY_URL+REFERENCE",
...
]
}
GET /api/v1/categories/{reference}/relatives/
- reference: category's reference.
{
"url": "CATEGORY_URL+REFERENCE",
"name": "NAME",
"channel": "CHANNEL_URL+REFERENCE",
"parent": {
"url": "CATEGORY_URL+REFERENCE",
"name": "NAME",
"parent": {...}
},
"children": [
{
"url": "CATEGORY_URL+REFERENCE",
"name": "NAME",
"children": [...]
},
...
]
}