Skip to content
Merged
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
83 changes: 80 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,83 @@
# django_utils
Django utility functions and libraries that I have written over time and regularly use.
Django utility functions and libraries that I have written over time and regularly use. All the libraries/functions are inside the `common` directory.

Details about the libraries included to help you get started.

Hola,
I would be updating and properly documenting this in a few days. Keep watching this repo for updates.
#### `common.external`
External libraries that I use in my django projects.
* `common.external.showme` -- This is an external package that I use for profiling function times. The most useful decorator of this library is `showme.time`
#### `common.logging`
Use this package to add logging to your django rest framework ViewSets. Following are the steps required for using this package:
* Add `common.logging.middleware.LoggingMiddleware` to the django setting `MIDDLEWARE_CLASSES`.
* Configure the logging setting in django settings. You can refer `django_utils.settings.LOGGING` for this purpose
* Use the mixin `common.logging.mixins.APILoggingMixin` in your ViewSets

#### `common.utils`
Set of utility functions/libraries that can be imported into any python app.
* #### `utils.admin_filters`
Filters that can be imported into any django admin to give additional support.
Currently, the `IsNullBlank` filter is available, which adds an additional set of django admin filters with options as
`Has Value`, `None` (for fields with `null=True`), `Blank` (fields with `blank=True`) & `Null or Blank` (fields with both `null=True, blank=True`). Inherit this filter to create custom filter as follows:
```python
from common.utils.admin_filters import IsNullBlankFilter
class PostTitleFilter(IsNullBlankFilter):
title = 'Post Title'
parameter_name = 'title'
show_blank_filter = True
show_null_filter = False
```
* #### `utils.date_ops`
Set of functions for retrieving/constructing date time objects and time ranges. Refer the function documentation for details. Usage as follows:
```python
from common.utils.date_ops import DateTimeOperations as DtOps
print(DtOps.ist_datetime(2017, 4, 14))
print(DtOps.ist_now())
print(DtOps.get_range_calendar_year())
```
* #### `utils.exception`
Use `ExceptionLogger` for detailed logging of exceptions inside your code. Usage as follows:
```python
from common.utils.exception import ExceptionLogger
try:
# do something
raise Exception
except:
ExceptionLogger.print_exception()
```
* #### `utils.file_ops`
Set of functions to delete files and create/remove/recreate directories. Usage is self-explanatory.
* #### `utils.http`
Primarily used for creating django responses with downloadable file objects.
* #### `utils.logged_requests`
Python's `requests` library enhanced with extensive logging. This library contains two sets of functions as follows:
* `LoggedRequests` -- wrapper over the vanilla requests methods. Usage as follows:
```python
from common.utils.logged_requests import LoggedRequests
LoggedRequests.get('http://icanhazip.com', log_title='Demo for LoggedRequests ')
```
* `PatchedRequests` -- monkey patches the vanilla requests functions with logging details. The effect of using this is entire project wide. Usage as follows:
```python
# inside __init__.py
from common.utils.logged_requests import PatchedRequests
PatchedRequests.patch_library()

# For sending requests in any other file use the vanilla request library as is with additional parameters log_title & log_response_data
import requests
requests.get('http://icanhazip.com', log_title='Demo for LoggedRequests ') # Gives the same result as LoggedRequests.get
```
* #### `utils.model_fields`
Custom fields used across django models:
* `DefaultTZDateTimeField` -- A wrapper over `models.DateTimeField` that converts db value of datetime fields to django setting's timezone.
* `CurrencyField` -- A wraper over `models.FloatField` that saves and retrieves numbers as 2-decimal precision values for monetary calculations.
* #### `utils.models`
Contains `MetaDataModel` which has the basic meta data fields that ideally every model object should have.
* #### `utils.queryset`
Contains `QuerysetHelpers`, a set of functions to help create querysets dynamically.
* #### `utils.s3`
Set of functions to access amazon s3 buckets & push/pull objects to/from the same.
* #### `utils.validators`
Set of functions that I use across my projects for variable validations.
* #### `utils.vars`
Set of variables that I use for my personal semantic understandings.

Feel free to use/modify/share the libraries & functions as per your discretion. Happy Coding.
2 changes: 2 additions & 0 deletions common/logging/adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from .builders import MessageBuilder
from .mixins import LoggingHelperMixin

__all__ = ['MessageAdapter', 'StrictMessageAdapter', 'LoggingAdapter', 'StrictLoggingAdapter']


class MessageAdapter(logging.LoggerAdapter):
def __init__(self, logger, *args, **kwargs):
Expand Down
2 changes: 2 additions & 0 deletions common/logging/message_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from .builders import MessageBuilder

__all__ = ['SYSTEM_IN', 'SYSTEM_OUT', 'EXCEPTION', 'API_IN_WITH_DATA', 'API_OUT_WITH_DATA', 'API_IN', 'API_OUT']

MESSAGE_TYPE = namedtuple('MESSAGE_TYPE', 'format builder')

MESSAGE_TYPE.__new__.__defaults__ = (MessageBuilder,)
Expand Down
2 changes: 2 additions & 0 deletions common/logging/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from . import message_types
from .adapters import LoggingAdapter

__all__ = ['LoggingMiddleware', 'ExceptionLoggingMiddleware']

logger = LoggingAdapter(logging.getLogger(__name__))


Expand Down
2 changes: 2 additions & 0 deletions common/logging/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from . import message_types

__all__ = ['APILoggingMixin']


class LoggingHelperMixin:
DEFAULT_VALUE = 'NA'
Expand Down
33 changes: 18 additions & 15 deletions common/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
import linecache
import sys
import inspect
import logging

from common.utils.exception import ExceptionLogger
from common.utils.vars import unauthorized


# http://stackoverflow.com/a/20264059/2422840
def print_exception():
exc_type, exc_obj, tb = sys.exc_info()
f = tb.tb_frame
lineno = tb.tb_lineno
filename = f.f_code.co_filename
linecache.checkcache(filename)
line = linecache.getline(filename, lineno, f.f_globals)
print('EXCEPTION IN:\nFILE:{}\nLINE No. {}\nLINE:"{}"\nERROR:{}'.format(
filename, lineno, line.strip(), exc_obj))


def parse_dict(request_dict, params_map):
"""
Parses a dictionary & retrieves the parameters as per the params mapping
Expand Down Expand Up @@ -63,7 +52,7 @@ def parse_dict(request_dict, params_map):
elif has_default:
result_params[to_name] = param_default
except:
print_exception()
ExceptionLogger.print_exception()
# Invalid data type passed in for parameter value. Skip value assignment

return result_params
Expand All @@ -84,3 +73,17 @@ def split_str(string, data_type=str):
split_string = [data_type(item) for item in split_string]

return split_string


def get_caller_file(stack_depth=1):
try:
return inspect.stack()[stack_depth + 1].filename.replace('{}/'.format(os.getcwd()), '')
except:
return None


def get_caller_logger(stack_depth=1, default_logger=None):
caller_file = get_caller_file(stack_depth=stack_depth + 1)
if caller_file:
return logging.getLogger(caller_file)
return default_logger
5 changes: 5 additions & 0 deletions common/utils/admin_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _

__all__ = ['IsNullBlankFilter']


class IsNullBlankFilter(SimpleListFilter):
"""
Adds additional filter to django admin for fields which have null=True and/or blank=True
"""
title = 'Target Field'
parameter_name = 'target_field'
show_blank_filter = False
Expand Down
72 changes: 72 additions & 0 deletions common/utils/date_ops.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from datetime import datetime, date

import arrow
import numpy as np
import pytz
from django.utils import timezone

Expand Down Expand Up @@ -80,3 +81,74 @@ def ist_now_arrow(cls):
get arrow object of current timestamp in ist format
"""
return arrow.now(tz=pytz.timezone('Asia/Kolkata'))

@classmethod
def num_weekdays(cls, start, end):
"""
returns number of days b/w start_date & end_date, excluding Sundays
"""
days = np.busday_count(start.date(), end.date(), weekmask='1111110')
return int(days)

@classmethod
def get_range_today(cls):
"""
returns the start and end timestamps for current date
"""
today = cls.ist_now_arrow()
start, end = today.span('day')
return start.datetime, end.datetime

@classmethod
def get_range_yesterday(cls):
"""
returns the start and end timestamps for yesterday
"""
yesterday = cls.ist_now_arrow().replace(days=-1)
start, end = yesterday.span('day')
return start.datetime, end.datetime

@classmethod
def get_range_current_week(cls):
"""
returns the start and end timestamps for current week
"""
today = cls.ist_now_arrow()
start, end = today.span('week')
return start.datetime, end.datetime

@classmethod
def get_range_last_week(cls):
"""
returns the start and end timestamps for last week
"""
today = cls.ist_now_arrow()
start, end = today.replace(weeks=-1).span('week')
return start.datetime, end.datetime

@classmethod
def get_range_current_month(cls):
"""
returns the start and end timestamps for current month
"""
today = cls.ist_now_arrow()
start, end = today.span('month')
return start.datetime, end.datetime

@classmethod
def get_range_last_month(cls):
"""
returns the start and end timestamps for last month
"""
today = cls.ist_now_arrow()
start, end = today.replace(month=-1).span('month')
return start.datetime, end.datetime

@classmethod
def get_range_calendar_year(cls):
"""
returns the start and end timestamps for current calendar year
"""
today = cls.ist_now_arrow()
start, end = today.span('year')
return start.datetime, end.datetime
46 changes: 46 additions & 0 deletions common/utils/exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import linecache
import logging
import sys

__all__ = ['ExceptionLogger']
logger = logging.getLogger(__name__)


class ExceptionLogger:
# http://stackoverflow.com/a/20264059/2422840
@staticmethod
def print_exception():
exc_type, exc_obj, tb = sys.exc_info()
f = tb.tb_frame
lineno = tb.tb_lineno
filename = f.f_code.co_filename
linecache.checkcache(filename)
line = linecache.getline(filename, lineno, f.f_globals)
error_details = 'EXCEPTION IN:\nFILE:{}\nLINE No. {}\nLINE:"{}"\nERROR:{}'.format(
filename, lineno, line.strip(), exc_obj)
print(error_details)

@staticmethod
def log_exception(logger):
exc_type, exc_obj, tb = sys.exc_info()
f = tb.tb_frame
lineno = tb.tb_lineno
filename = f.f_code.co_filename
linecache.checkcache(filename)
line = linecache.getline(filename, lineno, f.f_globals)
error_details = 'EXCEPTION IN:\nFILE:{}\nLINE No. {}\nLINE:"{}"\nERROR:{}'.format(
filename, lineno, line.strip(), exc_obj)
logger.error(error_details)

@staticmethod
def print_and_log_exception(logger):
exc_type, exc_obj, tb = sys.exc_info()
f = tb.tb_frame
lineno = tb.tb_lineno
filename = f.f_code.co_filename
linecache.checkcache(filename)
line = linecache.getline(filename, lineno, f.f_globals)
error_details = 'EXCEPTION IN:\nFILE:{}\nLINE No. {}\nLINE:"{}"\nERROR:{}'.format(
filename, lineno, line.strip(), exc_obj)
print(error_details)
logger.error(error_details)
2 changes: 2 additions & 0 deletions common/utils/file_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import os
import shutil

__all__ = ['FileOperations']


class FileOperations:
"""
Expand Down
10 changes: 6 additions & 4 deletions common/utils/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
from django.http import HttpResponse

from common.utils.file_ops import FileOperations as FOps
from common.utils import print_exception
from common.utils.exception import ExceptionLogger

__all__ = ['HttpOperations']


class HttpOperations:
Expand All @@ -33,7 +35,7 @@ def downloadable_file(cls, file_path, download_name):
response['Content-Length'] = os.path.getsize(file_path)
response['Content-Disposition'] = "attachment; filename={}".format(download_name)
except:
print_exception()
ExceptionLogger.print_exception()
return None

return response
Expand All @@ -51,7 +53,7 @@ def retrieve_image(cls, url):
if response.status_code == 200:
image = BytesIO(response.content)
except:
print_exception()
ExceptionLogger.print_exception()
return None

return image
Expand All @@ -74,5 +76,5 @@ def download_to_temp(cls, file_url, download_name):

return file_path
except:
print_exception()
ExceptionLogger.print_exception()
return None
Loading