Django REST Demo is a Django REST Framework project reference for a City, Country, and Currency fixture API.
The codebase associated with this project provides a comprehensive implementation of the following features:
-
Heroku-compatible environment and dependency management using the Pipenv library -- See Pipfile | Pipfile.lock.
-
Environment-specific project settings with a
.env
file and the Python Decouple library -- See settings.py | .env -
Custom User model that uses an email address instead of a username -- See settings.py | user.models.py
-
Manual fixture migrations for initial database population -- See populate_locations.py | populate_currencies.py
-
PostgreSQL database configuration settings in conjunction with the psycopg2 database adapter -- See settings.py
-
Amazon AWS S3-compatible static and media file hosting configuration settings in conjunction with the boto3 and django storages libraries -- See settings.py | storages.py
-
Celery and Redis-compatible project settings for scheduled and background tasks in conjunction with Celery and redis-py -- See settings.py | celery.py | Procfile | currency.tasks.py
-
Nested API routes that model database relations in conjunction with the drf-nested-routers library -- See api.urls
-
Searchable, sortable, and filterable endpoints with side-loadable and traversable relations in conjunction with the Dynamic REST library -- See serializers.py and views.py in each project submodule.
-
Unit testing for models and views -- See test_models.py and test_views.py in the tests directory of each project submodule.
-
Heroku deployment-ready project settings and release script in conjunction with the Django-Heroku library -- See settings.py | Procfile | release.sh
The live demo of this project is hosted on a Heroku free tier pipeline and may take up to 30 seconds to wake up.
For convenience, the Django REST Framework browsable API feature has been enabled so that you can easily navigate through the following publicly available endpoints:
Endpoint | Description |
---|---|
api / cities / | All 2,174 cities in database |
api / countries / | All 250 countries in database |
api / countries / :id / cities / (:id) / | All cities that belong to a given country |
api / currencies / | All 114 currencies in database |
api / currencies / :id / countries / (:id) / | All countries that use a given currency |
Note that the country and currency endpoints accept ID lookups as well as field lookups.
The country endpoint accepts ID, ISO2, and ISO3 lookups; the following return the same response:
The currency endpoint accepts ID and currency code lookups; the following return the same response:
This project utilizes sideloading as opposed to nested serializers to present related data for the following reasons:
-
Retains a flatter data structure more closely representing that of a row in an SQL database
-
Reduces data duplication in cases where many objects share a relation thus reducing overall payload; a nested serializer will repeat data for every shared relation while a sideloaded relation need only appear once
-
Allows the client to choose which sideloaded relations to fetch, if any, without affecting the underlying data structure of the default response
The following request will fetch all available cities and sideload their country foreign key relation:
The following request will fetch all available countries and sideload their capital and currency foreign key relations:
The following request will fetch all available currencies and sideload their country foreign key relation:
Each endpoint in this project offers a general search or "autocomplete" feature that will perform a case-insensitive string search across one or multiple pre-defined fields.
In the case of the city endpoint, search will return results matching City.name
:
The country endpoint search will return results matching Country.name
and Country.name_native
:
Finally, the currency endpoint search will return results matching Currency.name
, Currency.code
, Currency.number
, and Currency.country.name
:
- api / currencies / ? search = dollar
- api / currencies / ? search = sd
- api / currencies / ? search = 40
- api / currencies / ? search = united
Each endpoint in this project supports sorting results by one or more fields, and in either direction.
The following requests will sort all available countries by name, ascending and descending respectively:
The following requests will sort all available countries by population, ascending and descending respectively:
Sorting may also be applied accross database relations through dot notation.
The following request will sort all available countries by descending capital population, i.e. largest first:
Note that results with null values appear at the end of ascending lists by default, which means they appear at the beginning of the above results list. Refer to the Filtering section for an example of how to omit them altogether.
You may also combine multiple fields into a single sort operation with an ampersand.
The following request will sort all available countries by United Nations member status and then by name:
Each endpoint in this project supports filtering results by one or more field values.
Once again, dot notation can be used to apply a filter operation accross database relations, and ampersands may be used to combine multiple filter operations into a single request.
The exact
filter will match results based on the exact value of a given field or set of fields:
The following request will return a list of countries that are currently UN member states:
The following request will return a list of countries that became UN member states when the UN was founded:
The following request will return a list of countries that use the US Dollar as their primary currency:
Note that the above request will return results that are equivalent to those of the country of currency endpoint:
Note also that the iexact
modifier can be added to string queries to make them case-insensitive:
Furthermore, we'll notice that relation traversal applies to child relations as well as parent relations.
The following request will return a list of countries who have at least one city named "Georgetown":
The in
filter will match results based on whether the exact value of a given field is one of a list of values.
The following request will return a list of countries consisting of the United States and Australia:
The following request will return a list of countries that use either the US Dollar or the Australian Dollar:
The contains
filter will match results based on whether the value of a given string field contains a substring.
The following request will return a list of countries whose name contains "den", i.e. Sweden:
Note that an i
modifier can be prefixed to this filter to make it case insensitive.
The following request will do the same without regard to case, i.e. Denmark and Sweden:
The startswith
and endswith
filters match results based on whether the value of a given string field either starts or ends with a specified substring. Again, the i
modifier can be applied to make them case-insenstive.
The following requests will return a list of countries whose name starts with "United":
- api / countries / ? filter{name.startswith} = United
- api / countries / ? filter{name.istartswith} = united
The following requests will return a list of countries whose name ends with the suffix "-ia":
The gt
and lt
filters match results based whether the value of a given field is either greater than or less than a specified value. In this case, an e
modifier can be suffixed to these filters to make them inclusive (equal to).
The following requests will return a list of countries whose population is greater than (or equal to) one billion:
- api / countries / ? filter{population.gt} = 1000000000
- api / countries / ? filter{population.gte} = 1000000000
The following requests will return a list of countries whose population is less than (or equal to) five million:
- api / countries / ? filter{population.lt} = 5000000
- api / countries / ? filter{population.lte} = 5000000
The following request will return a list of countries whose capital population is greater than or equal to ten million:
The following request will return a list of countries with at least one city whose population is greater than or equal to ten million:
The following request will return a list of currencies that do not have a primary issuing country, e.g. the Euro:
The following request will return a list of countries whose capitals have a non-null population:
The results of any of the above filter operations can also be inverted in a similar way a reverse sort works.
The following request returns a list of countries whose names DO NOT start with the word "United":
The following request returns a list of countries that DO NOT use the US Dollar as their primary currency:
Refer to the Dynamic REST repository and documentation for further information on supported operations.
Clone this repository into a directory of your choosing:
$ git clone https://github.com/khunspoonzi/drf-demo.git
Navigate into the root directory of the newly cloned repository:
$ cd drf-demo
Rename .env.example
to .env
so that your environment variables are read into the Django settings module.
If you don't already have Pipenv
installed in your machine, install it now:
$ pip install --user pipenv
Initialize a Pipenv
shell:
$ pipenv shell
Install project dependencies:
$ pipenv install
Assuming you have USE_LOCAL_STORAGE
set to True
in your .env
, be sure to collect your static files locally:
$ python manage.py collectstatic
If you haven't already installed Postgres on your machine, do so now.
Create a Postgres database named drf-demo
. On a Linux system, the following suffices:
$ sudo -u postgres createdb "drf-demo"
Initialize your database tables and populate your database fixtures:
$ python manage.py migrate
Run the local server:
$ python manage.py runserver
If you wish to test background and scheduled tasks, be sure to install Redis and start your server this way:
$ heroku local
The above command will invoke your Celery worker and connect it to Redis in addition to starting your server.
If you wish to log into and access the Django Admin panel, be sure to create a superuser first:
$ python manage.py createsuperuser
You'll be able to log in via the /admin/
route of your running server.