Skip to content

protozeit/web-coding-challenge

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

57 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Web Coding Challenge

The coding challenge is about implementing an app that lists shops nearby.

This README will also serve as a checklist during the development process.

Technical spec

  • Your application should be split between a back-end and a web front-end.

  • The front-end should ideally be a single page app with a single index.html linking to external JS/CSS/etc.

    • This criteria is respected excluding the login and signup page.

back-end: Django REST framework

Functional spec

  • As a User, I can sign up using my email & password
  • As a User, I can sign in using my email & password
  • As a User, I can display the list of shops sorted by distance
  • As a User, I can like a shop, so it can be added to my preferred shops
  • Liked shops disappear from nearby shops and vice-versa
  • Bonus points (those items are optional):
  • As a User, I can dislike a shop, so it won’t be displayed within “Nearby Shops” list during the next 2 hours
  • As a User, I can display the list of preferred shops
  • As a User, I can remove a shop from my preferred shops list

Start up Guide

Preferably set up a clean python virtual environment, with Python 3, you can use the venv module to create a virtual environment for your project:

λ python -m venv py
λ workon py

cd into the api directory and run:

(py) λ pip install -r requirements.txt

You might still need to install GDAL after this. If you're on windows you might also run into this problem. In my case I had to modify the (Python Root)\Lib\site-packages\django\contrib\gis\gdal\libgdal.py file and my particular version of GDAL (gdal204) into the libnames array below:

if lib_path:
    lib_names = None
elif os.name == 'nt':
    # Windows NT shared libraries
    lib_names = ['gdal204', 'gdal203', 'gdal202', 'gdal201', 'gdal20', 'gdal111']
elif os.name == 'posix':
    # *NIX library names.
    lib_names = ['gdal', 'GDAL', 'gdal2.3.0', 'gdal2.2.0', 'gdal2.1.0', 'gdal2.0.0', 'gdal1.11.0']
else:
    raise ImproperlyConfigured('GDAL is unsupported on OS "%s".' % os.name)

The Django REST api talks to a Postgis database to allow for location related calculations. A docker container can be used for easy set up:

(py) λ docker run --name=postgisdb -d -e POSTGRES_USER=admin -e POSTGRES_PASS=admin -e POSTGRES_DBNAME=gis -p 9000:5432 kartoza/postgis

Make sure the parameters you pass in the docker command are the same as those in the settings.py file:

DATABASES = {
    'default': {
        'ENGINE': 'django.contrib.gis.db.backends.postgis',
        'NAME': 'gis',
        'USER': 'admin',
        'PASSWORD': 'admin',
        'HOST': 'localhost',
        'PORT': '9000',
    },
}

Now you should be ready to migrate the models:

(py) λ python manage.py makemigrations
(py) λ python manage.py migrate
(py) λ python manage.py runserver

The api should be up now at localhost:8000.

API documentation

A GET request to (API_ROOT)/shops?lon=-6.8243&lat=33.80086&id=1 will return a list of shops sorted by ascending distances (in kilometers) from the specified coordinates. Ommiting the parameters will return an unsorted list. The user id is used to exclude already liked shops from showing up on the main page.

GET /shops/?lon=-6.8243&lat=33.80086
HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

[
    {
        "id": 533,
        "name": "Kiggle",
        "picture": "http://placehold.it/150x150",
        "_id": "5a0c6755fb3aac66aafe26e0",
        "location": "SRID=4326;POINT (-6.8244 33.80087)",
        "email": "leilaware@kiggle.com",
        "city": "Rabat",
        "distance": "0.0"
    },
    {
        "id": 602,
        "name": "Proflex",
        "picture": "http://placehold.it/150x150",
        "_id": "5a0c680afd3eb67969316d0e",
        "location": "SRID=4326;POINT (-6.82649 33.80586)",
        "email": "leilaware@proflex.com",
        "city": "Rabat",
        "distance": "0.6"
    },
    {
        "id": 648,
        "name": "Isosphere",
        "picture": "http://placehold.it/150x150",
        "_id": "5a0c687dfd3eb67969316d3c",
        "location": "SRID=4326;POINT (-6.82254 33.80692)",
        "email": "leilaware@isosphere.com",
        "city": "Rabat",
        "distance": "0.7"
    },
    .
    .
    .

You can also query specific shops using their id (API_ROOT)/shops/602?lon=-6.8243&lat=33.80086:

GET /shops/602/?lon=-6.8243&lat=33.80086
HTTP 200 OK
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 602,
    "name": "Proflex",
    "picture": "http://placehold.it/150x150",
    "_id": "5a0c680afd3eb67969316d0e",
    "location": "SRID=4326;POINT (-6.82649 33.80586)",
    "email": "leilaware@proflex.com",
    "city": "Rabat",
    "distance": "0.6"
}

With pagination enabled the response looks a bit different:

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 15
}
GET /shops/?lon=-5.5860465999999995&lat=34.7963763

HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "count": 342,
    "next": "http://localhost:8000/shops/?lat=34.7963763&limit=15&lon=-5.5860465999999995&offset=15",
    "previous": null,
    "results": [
        {
            "id": 718,
            "name": "Filodyne",
            "picture": "http://placehold.it/150x150",
            "_id": "5a0c6b42fd3eb67969316d82",
            "location": "SRID=4326;POINT (-6.747 33.98196)",
            "email": "leilaware@filodyne.com",
            "city": "Rabat",
            "distance": "139.82"
        },
        {
            "id": 698,
            "name": "Filodyne",
            "picture": "http://placehold.it/150x150",
            "_id": "5a0c6b2dfd3eb67969316d6e",
            "location": "SRID=4326;POINT (-6.747 33.98196)",
            "email": "leilaware@filodyne.com",
            "city": "Rabat",
            "distance": "139.82"
        },
        .
        .
        .

The authentication endpoints that are used are (API_ROOT)/rest-auth/login, (API_ROOT)/rest-auth/logout and (API_ROOT)/rest-auth/registration. For more specific information on each, check the official documentation

For the like/dislike functionality, a partial update api view is exposed at (API_ROOT)/like/<pk>/ and a list api view at (API_ROOT)/prefered/?id=<user_id>

Comments

Reviewers might notice that some functionality that should idealy be handled by the back-end was instead relagated to the front-end instead. This is not a delibrate choice, nor an oversight. Indeed, I would have quite like to make the api much more robust and extensible, but only so much could be acomplished within a week of learning the basics of the framework.

Screenshots

Imgur Imgur Imgur Imgur

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Python 48.6%
  • JavaScript 26.5%
  • HTML 22.0%
  • CSS 2.9%