Skip to content

Ft admin asset Type threshold notification #329

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ This repository contains the API endpoints and models for the ART project implem
| <sup>**RETRY_TIMEOUT**</sup> | <sup>**Optional** - Number of seconds to wait before retrying an external request (currently to AIS) if an error other than 401 is received.</sup> | <sup>**10**</sup> |
| <sup>**LOGLEVEL**</sup> | <sup>**Optional** - Default log level - error, warning, info, debug.</sup> | <sup>**info**</sup> |
| <sup>**ADMINS**</sup> | <sup>**Optional** - Email addresses to send error logs to.</sup> | <sup>**art:[email protected],art_group:[email protected]**</sup> |
| <sup>**DEFAULT_THRESHOLD**</sup> | <sup>**REQUIRED** - Minimal asset threshold</sup> | <sup>**Integer value EG 20**</sup>|
| <sup>**EMAIL_HOST**</sup> | <sup>**REQUIRED** - email host.</sup> | <sup>**smtp.gmail.com**</sup> |
| <sup>**EMAIL_HOST_USER**</sup> | <sup>**REQUIRED** - email host user account</sup> | |
| <sup>**EMAIL_HOST_PASSWORD**</sup> | <sup>**REQUIRED** - email host user account password</sup> | |
| <sup>**EMAIL_PORT**</sup> | <sup>**REQUIRED** - email port.</sup> | <sup>**587**</sup> |
| <sup>**EMAIL_USE_TLS**</sup> | <sup>**REQUIRED** - email TLS.</sup> | <sup>**True**</sup> |
| <sup>**EMAIL_SENDER**</sup> | <sup>**REQUIRED** - email sender's address.</sup> | |


### Project setup
#### Installation script
Expand All @@ -60,6 +68,7 @@ The easiest way to set up is to run `. ./install_art.sh` which does the followin
- Install the project dependencies stored in [Pipfile](/Pipfile). Run `pipenv install --dev`.
- Run migrations - `python manage.py migrate`
- Create cache table `python manage.py createcachetable`
-Open a terminal tab then run the command `python manage.py qcluster` to start django q to enable in sending email notifications

#### Development using Docker
To use the Docker setup, ensure you have Docker installed then run the following commands:
Expand Down
19 changes: 19 additions & 0 deletions api/send_email.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Third-Party Imports
from decouple import config
from django_q.tasks import async_task

# App Imports
from core.constants import MSG, SUBJECT
from core.models.user import User


def send_email(data):
""""This method sends emails to users"""
for user in User.objects.filter(is_staff=True):
async_task(
"django.core.mail.send_mail",
SUBJECT,
MSG.format(data.name),
config("EMAIL_SENDER"),
[user.email],
)
1 change: 1 addition & 0 deletions api/serializers/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ class Meta:
"created_at",
"last_modified",
"asset_type",
"threshold",
)

def to_internal_value(self, data):
Expand Down
6 changes: 4 additions & 2 deletions api/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,12 @@ def setUpClass(cls):
name="Sub Category nameseses", asset_category=cls.asset_category
)
cls.asset_type = apps.get_model("core", "AssetType").objects.create(
name="Asset Types", asset_sub_category=cls.asset_sub_category
name="Asset Types", asset_sub_category=cls.asset_sub_category, threshold=50
)
cls.test_asset_type = apps.get_model("core", "AssetType").objects.create(
name="Other Asset Types", asset_sub_category=cls.asset_sub_category
name="Other Asset Types",
asset_sub_category=cls.asset_sub_category,
threshold=50,
)
cls.asset_make = apps.get_model("core", "AssetMake").objects.create(
name="Asset Makes", asset_type=cls.asset_type
Expand Down
32 changes: 31 additions & 1 deletion api/tests/test_asset_type_api.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# Standard Library
import os
from unittest.mock import patch

# Third-Party Imports
from django.test import override_settings
from rest_framework.test import APIClient

# App Imports
from api.tests import APIBaseTestCase
from core.models import AssetType
from core import constants
from core.models import AllocationHistory, AssetType, User

client = APIClient()

Expand All @@ -26,6 +29,7 @@ def test_can_post_asset_type(self, mock_verify_token):
data = {
"name": "Asset Type Example",
"asset_sub_category": self.asset_sub_category.id,
"threshold": 50,
}
response = client.post(
self.asset_type_url,
Expand Down Expand Up @@ -112,3 +116,29 @@ def test_asset_type_api_orders_asset_types_by_type(self, mock_verify_id_token):
# since the asset types are ordered.
self.assertEqual(AssetType.objects.count(), len(response.data.get("results")))
self.assertEqual(response.data.get("results")[-1].get("name"), "Samsung")

@patch("api.send_email.async_task")
@patch("api.authentication.auth.verify_id_token")
@override_settings(EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend")
def test_email_is_sent_when_asset_type_threshold_critical(
self, mock_verify_token, mail
):
mock_verify_token.return_value = {"email": self.admin_user.email}
assettype = AssetType(
name="Andelaheadsets",
asset_sub_category=self.asset_sub_category,
threshold=20,
)
assettype.save()
self.asset_make.asset_type = assettype
self.asset_make.save()
allocate = AllocationHistory(
asset=self.asset_1, current_assignee=self.asset_assignee
)
allocate.save()
data = User.objects.filter(is_staff=True)
emails = mail._mock_call_args_list[0]
self.assertEqual(emails[0][1], constants.SUBJECT)
self.assertEqual(emails[0][3], os.getenv("EMAIL_SENDER"))
self.assertEqual(emails[0][2], constants.MSG.format(assettype.name))
self.assertEqual(emails[0][4][0], data[0].email)
3 changes: 3 additions & 0 deletions core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,6 @@
(WARNING_NOTIFICATION, WARNING_NOTIFICATION),
(INFO_NOTIFICATION, INFO_NOTIFICATION),
)
SUBJECT = "STOCK LEVEL CRITICAL"

MSG = "Dear Admin, The stock level for the Asset type {} have gone below the minimum threshold."
18 changes: 18 additions & 0 deletions core/migrations/0053_assettype_threshold.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.1.10 on 2019-08-26 12:56

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('core', '0052_notifications'),
]

operations = [
migrations.AddField(
model_name='assettype',
name='threshold',
field=models.IntegerField(default=0),
),
]
16 changes: 13 additions & 3 deletions core/models/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
from datetime import datetime

# Third-Party Imports
from decouple import config
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models

# App Imports
from api.send_email import send_email
from core import constants
from core.managers import CaseInsensitiveManager
from core.slack_bot import SlackIntegration
Expand All @@ -26,7 +28,7 @@ def user_abstract(user, filename):
:params: user -> user object
:params: filename -> string
"""
return f'user_{user}_{filename}'
return f"user_{user}_{filename}"


class AssetCategory(models.Model):
Expand Down Expand Up @@ -92,6 +94,7 @@ class AssetType(models.Model):
last_modified = models.DateTimeField(auto_now=True, editable=False)
asset_sub_category = models.ForeignKey("AssetSubCategory", on_delete=models.PROTECT)
has_specs = models.BooleanField(default=False)
threshold = models.IntegerField(default=0)

objects = CaseInsensitiveManager()

Expand Down Expand Up @@ -542,6 +545,13 @@ def save(self, *args, **kwargs):
self.previous_assignee = None
try:
super().save(*args, **kwargs)
if self.previous_assignee is None:
threshold_data = self.asset.model_number.asset_make.asset_type
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This variable would be more descriptive if named asset_type. Then you can have asset_type.threshold = ....

threshold_data.threshold = threshold_data.threshold - 1
if threshold_data.threshold <= int(config("DEFAULT_THRESHOLD")):
send_email(threshold_data)
threshold_data.save()

except Exception:
raise
else:
Expand Down Expand Up @@ -619,10 +629,10 @@ class AssetIncidentReport(models.Model):
loss_of_property = models.TextField(null=True, blank=True)
witnesses = models.TextField(null=True, blank=True)
police_abstract_obtained = models.CharField(max_length=255)
submitted_by = models.ForeignKey('User', null=True, on_delete=models.PROTECT)
submitted_by = models.ForeignKey("User", null=True, on_delete=models.PROTECT)
created_at = models.DateTimeField(default=datetime.now, editable=False)
police_abstract = models.FileField(
'Police Abstract', upload_to=user_abstract, blank=True
"Police Abstract", upload_to=user_abstract, blank=True
)

def __str__(self):
Expand Down
4 changes: 1 addition & 3 deletions settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@

REDOC_SETTINGS = {"LAZY_RENDERING": True}

# EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"

EMAIL_HOST = config("EMAIL_HOST", None)
EMAIL_PORT = config("EMAIL_PORT", None)
Expand All @@ -205,5 +205,3 @@
'LOCATION': 'art_cache',
}
}
# log emails in console rather than sending them
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'