Browse Source

Use channel to dispatch an action to check CPIC.

feature/bike-tracking
Drew Larson 8 years ago
parent
commit
970a9a3eef
  1. 3
      bikeshop_project/bike/apps.py
  2. 60
      bikeshop_project/bike/consumers.py
  3. 7
      bikeshop_project/bike/routing.py
  4. 16
      bikeshop_project/bike/signals.py
  5. 28
      bikeshop_project/bike/tests.py
  6. 11
      bikeshop_project/bikeshop/settings/base.py
  7. 5
      requirements/base.txt

3
bikeshop_project/bike/apps.py

@ -3,3 +3,6 @@ from django.apps import AppConfig
class BikeConfig(AppConfig):
name = 'bike'
def ready(self):
import bike.signals #noqa

60
bikeshop_project/bike/consumers.py

@ -0,0 +1,60 @@
import logging
import re
from typing import Dict, Union, Optional
import requests
from bs4 import BeautifulSoup
from django.core.exceptions import ObjectDoesNotExist
from django.utils import timezone
from bike.models import Bike
logger = logging.getLogger('cpic')
def _is_stolen(serial: str) -> Optional[bool]:
url = 'http://app.cpic-cipc.ca/English/searchFormResultsbikes.cfm'
data = {'ser': message.get('serial_number'),
'toc': 1,
'Submit': 'Begin Search'}
r = requests.post(url, data=data)
html = r.text
soup = BeautifulSoup(html)
no_records = r'^No Records were found in our database on.+$'
found_records = r'^WE HAVE A RECORD ON FILE THAT MATCHES THE IDENTIFIERS THAT YOU PROVIDED.+$'
if soup.body.findAll(text=re.compile(no_records)):
return False
elif soup.body.findAll(text=re.compile(found_records)):
return True
return None
def check_cpic(message: Dict[str, Union[str, int]]) -> None:
"""
Makes a remote call to CPIC to determine whether a bike has been stolen.
"""
try:
bike = Bike.objects.get(id=message['bike_id'])
except ObjectDoesNotExist:
logger.error(f'check_epic: Invalid Bike id: {message["bike_id"]}')
return
stolen = _is_stolen(message['serial_number'])
if stolen:
bike.cpic_searched_at = timezone.now()
bike.stolen = True
elif stolen is None:
logger.error(f'check_epic: Unable to check CPIC records with serial number: {message["serial_number"]}.')
return
else:
bike.cpic_searched_at = timezone.now()
bike.stolen = False
bike.save()

7
bikeshop_project/bike/routing.py

@ -0,0 +1,7 @@
from channels.routing import route
from .consumers import check_cpic
channel_routing = [
route('check-cpic', check_cpic),
]

16
bikeshop_project/bike/signals.py

@ -0,0 +1,16 @@
from channels import Channel
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Bike
@receiver(post_save, sender=Bike)
def bike_save_handler(sender, instance, created, **kwargs):
if created:
message = {
'bike_id': instance.id,
'serial_number': instance.serial_number,
}
Channel('check-cpic').send(message)

28
bikeshop_project/bike/tests.py

@ -6,8 +6,10 @@ from rest_framework.test import APIClient
from model_mommy import mommy
from rest_framework import status
from bike.consumers import check_cpic
from registration.models import Member
from .models import Bike, BikeState
from unittest.mock import patch
class TestGet(TestCase):
@ -344,3 +346,29 @@ class TestGet(TestCase):
result = client.put(f'/api/v1/bikes/{bike.id}/stolen/')
self.assertEqual(result.status_code, status.HTTP_200_OK)
class TestBikeSignals(TestCase):
@patch('bike.consumers._is_stolen')
def test_check_cpic_stolen_bike(self, is_stolen_mock):
bike = mommy.make(Bike)
message = {'bike_id': bike.id, 'serial_number': bike.serial_number}
is_stolen_mock.return_value = True
check_cpic(message)
updated_bike = Bike.objects.get(id=bike.id)
self.assertTrue(updated_bike.stolen)
self.assertIsNotNone(updated_bike.cpic_searched_at)
@patch('bike.consumers._is_stolen')
def test_check_cpic_not_stolen_bike(self, is_stolen_mock):
bike = mommy.make(Bike)
message = {'bike_id': bike.id, 'serial_number': bike.serial_number}
is_stolen_mock.return_value = False
check_cpic(message)
updated_bike = Bike.objects.get(id=bike.id)
self.assertFalse(updated_bike.stolen)
self.assertIsNotNone(updated_bike.cpic_searched_at)

11
bikeshop_project/bikeshop/settings/base.py

@ -33,6 +33,7 @@ INSTALLED_APPS = [
'webpack_loader',
'compressor',
'rest_framework',
'channels',
'registration',
'core',
@ -176,3 +177,13 @@ REST_FRAMEWORK = {
}
DATE_INPUT_FORMATS = ['iso-8601']
CHANNEL_LAYERS = {
"default": {
"BACKEND": "asgi_redis.RedisChannelLayer",
"CONFIG": {
"hosts": [os.environ.get('REDIS_URL', 'redis://redis:6379')],
},
"ROUTING": "bike.routing.channel_routing",
},
}

5
requirements/base.txt

@ -12,4 +12,7 @@ djangorestframework
django-webpack-loader
requests
PyYAML
djangorestframework-jwt==1.9.0
djangorestframework-jwt==1.9.0
channels
asgi-redis
beautifulsoup4

Loading…
Cancel
Save