Skip to content

Commit e8adca0

Browse files
author
Dmitry Berezovsky
committedJun 19, 2017
Initial version
1 parent 8d66432 commit e8adca0

31 files changed

+611
-2
lines changed
 

‎.gitignore

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
.idea
12
# Byte-compiled / optimized / DLL files
23
__pycache__/
34
*.py[cod]
@@ -8,7 +9,7 @@ __pycache__/
89

910
# Distribution / packaging
1011
.Python
11-
env/
12+
/env/
1213
build/
1314
develop-eggs/
1415
dist/
@@ -85,7 +86,6 @@ celerybeat-schedule
8586
# virtualenv
8687
.venv
8788
venv/
88-
ENV/
8989

9090
# Spyder project settings
9191
.spyderproject

‎api_commons/__init__.py

Whitespace-only changes.

‎api_commons/common.py

+206
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import logging
2+
import sys
3+
from json import JSONEncoder
4+
from uuid import UUID
5+
6+
import collections
7+
from django.core.exceptions import ObjectDoesNotExist
8+
from django.http import HttpRequest
9+
from django.http import HttpResponse
10+
from rest_framework import status as HttpStatus
11+
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
12+
from rest_framework.exceptions import NotAuthenticated, AuthenticationFailed
13+
from rest_framework.renderers import JSONRenderer
14+
from rest_framework.response import Response
15+
from rest_framework.settings import api_settings
16+
from rest_framework.views import APIView
17+
from typing import Union
18+
19+
from .dto import BaseDto, ApiResponseDto
20+
from .error import InvalidPaginationOptionsError, InvalidInputDataError, ErrorCode
21+
22+
DtoOrMessageOrErrorCode = Union[BaseDto, str, ErrorCode]
23+
Payload = Union[collections.Iterable, BaseDto, None]
24+
25+
logger = logging.getLogger(__name__)
26+
27+
28+
class ApiResponse(Response):
29+
__SECRET = object()
30+
31+
def __init__(self, payload: Payload = None, status=None, template_name=None, headers=None, exception=False,
32+
content_type='application/json',
33+
secret=None) -> None:
34+
if secret != self.__SECRET:
35+
raise ValueError(
36+
"Using constructor of the ApiResponse is not allowed, please "
37+
"use static methods instead. See .success()")
38+
39+
assert payload is None \
40+
or isinstance(payload, collections.Iterable) \
41+
or isinstance(payload, BaseDto), "payload should be subclass of ApiResponse"
42+
super().__init__(None, status, template_name, headers, exception, content_type)
43+
api_response_dto = ApiResponseDto(payload)
44+
self.data = api_response_dto
45+
46+
@classmethod
47+
def __update_response_for_error(cls, response, dto_or_error_message: DtoOrMessageOrErrorCode, error_code,
48+
default_message):
49+
if isinstance(dto_or_error_message, ErrorCode):
50+
error_code = dto_or_error_message.error_code
51+
dto_or_error_message = dto_or_error_message.dto_or_error_message
52+
if isinstance(dto_or_error_message, BaseDto):
53+
response.data.service.validation_errors = dto_or_error_message.errors
54+
response.data.service.error_code = error_code if error_code is not None else response.status_code
55+
response.data.service.error_message = default_message \
56+
if isinstance(dto_or_error_message, BaseDto) else str(dto_or_error_message)
57+
58+
@classmethod
59+
def not_found(cls, dto_or_error_message: DtoOrMessageOrErrorCode, error_code=None):
60+
response = ApiResponse(None, status=HttpStatus.HTTP_404_NOT_FOUND, secret=cls.__SECRET)
61+
cls.__update_response_for_error(response, dto_or_error_message, error_code, "Entity not found")
62+
return response
63+
64+
@classmethod
65+
def success(cls, payload: [BaseDto, None] = None, status: int = HttpStatus.HTTP_200_OK):
66+
return ApiResponse(payload, status=status, secret=cls.__SECRET)
67+
68+
@classmethod
69+
def not_authenticated(cls, *args):
70+
response = ApiResponse(None, status=HttpStatus.HTTP_401_UNAUTHORIZED, secret=cls.__SECRET)
71+
if len(args) > 0:
72+
message = args[0]
73+
else:
74+
message = "Unauthorized"
75+
response.data.service.error_message = message
76+
return response
77+
78+
@classmethod
79+
def bad_request(cls, dto_or_error_message: Union[BaseDto, str], error_code: int = None):
80+
response = ApiResponse(None, status=HttpStatus.HTTP_400_BAD_REQUEST, secret=cls.__SECRET)
81+
cls.__update_response_for_error(response, dto_or_error_message, error_code,
82+
"Bad request. Check service.validation_errors for details")
83+
return response
84+
85+
@classmethod
86+
def internal_server_error(cls, exception: Exception = None):
87+
response = ApiResponse(None, status=HttpStatus.HTTP_500_INTERNAL_SERVER_ERROR, secret=cls.__SECRET)
88+
response.data.service.error_code = HttpStatus.HTTP_500_INTERNAL_SERVER_ERROR
89+
return response
90+
91+
92+
class JsonEncoder(JSONEncoder):
93+
def default(self, o):
94+
if isinstance(o, BaseDto):
95+
return o.to_dict()
96+
if isinstance(o, UUID):
97+
return str(o)
98+
return super().default(o)
99+
100+
101+
class CsrfExemptSessionAuthentication(SessionAuthentication):
102+
def enforce_csrf(self, request: HttpRequest):
103+
pass
104+
105+
106+
class JsonRenderer(JSONRenderer):
107+
encoder_class = JsonEncoder
108+
media_type = 'application/json'
109+
format = 'json'
110+
ensure_ascii = not api_settings.UNICODE_JSON
111+
compact = api_settings.COMPACT_JSON
112+
113+
def get_indent(self, accepted_media_type, renderer_context: dict):
114+
return None if self.compact else 4
115+
116+
def render(self, api_response: ApiResponseDto, accepted_media_type=None, renderer_context=None):
117+
assert isinstance(api_response, ApiResponseDto), "api_response should be an instance of ApiResponseDto"
118+
return super().render(api_response, accepted_media_type, renderer_context)
119+
120+
121+
class BaseController(APIView):
122+
renderer_classes = [JsonRenderer]
123+
authentication_classes = (CsrfExemptSessionAuthentication,)
124+
125+
def __init__(self, **kwargs):
126+
super().__init__(**kwargs)
127+
128+
def parse_int(self, str_value, field_name=None) -> int:
129+
try:
130+
return int(str_value)
131+
except ValueError:
132+
raise InvalidInputDataError('Invalid {} value. Should be integer.'.format(field_name), str_value)
133+
134+
def parse_int_pk(self, str_value, field_name=None) -> int:
135+
pk = self.parse_int(str_value)
136+
if pk <= 0:
137+
raise InvalidInputDataError('Invalid {} value. Should be positive integer'.format(field_name), str_value)
138+
return pk
139+
140+
def parse_bool_value(self, str_value, field_name=None) -> bool:
141+
try:
142+
return str_value.lower() not in ['false', 'no', '1', 'False']
143+
except ValueError:
144+
raise InvalidInputDataError('Invalid {} value. Should be boolean'.format(field_name), str_value)
145+
146+
def get_bool_param_from_url(self, request, param):
147+
create_node = True
148+
if request.query_params.get(param) is not None:
149+
create_node = self.parse_bool_value(request.query_params[param], param)
150+
return create_node
151+
152+
def get_string_param_from_url(self, request, param):
153+
if request.query_params.get(param) is not None:
154+
return request.query_params[param]
155+
156+
157+
class BasicAuthController(BaseController):
158+
authentication_classes = (CsrfExemptSessionAuthentication, BasicAuthentication,)
159+
160+
def __init__(self, **kwargs):
161+
super().__init__(**kwargs)
162+
163+
164+
def exception_handler(exc: Exception, context):
165+
logger.warning("Incoming request processing was terminated due to handled exception:", exc_info=exc)
166+
if isinstance(exc, InvalidPaginationOptionsError):
167+
return ApiResponse.bad_request('Bad limit and/or offset')
168+
if isinstance(exc, (NotAuthenticated, AuthenticationFailed)):
169+
return ApiResponse.not_authenticated()
170+
if isinstance(exc, ObjectDoesNotExist):
171+
return ApiResponse.not_found(exc)
172+
if isinstance(exc, InvalidInputDataError):
173+
return ApiResponse.bad_request(str(exc))
174+
else:
175+
logger.exception(str(exc))
176+
return ApiResponse.internal_server_error(exc)
177+
raise exc
178+
179+
180+
def __set_response_attributes(response: HttpResponse):
181+
response.accepted_renderer = JsonRenderer()
182+
response.accepted_media_type = JsonRenderer.media_type
183+
response.renderer_context = {"data": None}
184+
return response.render()
185+
186+
187+
def error_500_handler(request: HttpRequest):
188+
exec_info = sys.exc_info()
189+
logger.error('Internal Server Error: %s.', str(exec_info[1]),
190+
exc_info=exec_info,
191+
extra={
192+
'status_code': 500,
193+
'rs.path': request.path,
194+
'request': request
195+
})
196+
return __set_response_attributes(ApiResponse.internal_server_error(exec_info[1]))
197+
198+
199+
def error_404_handler(request: HttpRequest, exception: Exception):
200+
logger.warning('Not Found: %s. %s', request.path, str(exception),
201+
extra={
202+
'status_code': 404,
203+
'path': request.path,
204+
'request': request
205+
})
206+
return __set_response_attributes(ApiResponse.not_found(""))

‎api_commons/dto.py

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
from django.conf import settings
2+
from rest_framework.fields import empty, Field
3+
from rest_framework.serializers import Serializer
4+
5+
6+
class BaseDto(Serializer):
7+
def to_dict(self):
8+
if not hasattr(self, '_data'):
9+
return self.initial_data
10+
else:
11+
return self.data
12+
13+
def __init__(self, data=empty, **kwargs):
14+
self.initial_data = {}
15+
super(BaseDto, self).__init__(None, data, **kwargs)
16+
17+
def __setattr__(self, key, value):
18+
if key in self.get_declared_fields():
19+
self.initial_data[key] = value
20+
else:
21+
super().__setattr__(key, value)
22+
23+
def __getattr__(self, key):
24+
if key in self.get_declared_fields():
25+
return self.initial_data.get(key)
26+
else:
27+
if key in dir(super(BaseDto, self)):
28+
return getattr(super(), key)
29+
else:
30+
raise AttributeError("Object {} doesn't have attribute {}".format(self.__class__.__name__, key))
31+
32+
@classmethod
33+
def from_dict(cls, dictionary=empty):
34+
instance = cls(dictionary)
35+
return instance
36+
37+
@classmethod
38+
def get_declared_fields(cls):
39+
if hasattr(cls, '_declared_fields'):
40+
return getattr(cls, '_declared_fields')
41+
else:
42+
return []
43+
44+
45+
class ApiResponseServiceSection(BaseDto):
46+
def __init__(self):
47+
self.error_code = 0
48+
self.error_message = None
49+
self.validation_errors = []
50+
self.api_version = settings.API_VERSION
51+
52+
def is_successful(self):
53+
return self.error_code == 0
54+
55+
def to_dict(self):
56+
return {
57+
"error_code": self.error_code,
58+
"node_id": settings.HOSTNAME,
59+
"error_message": self.error_message,
60+
"validation_errors": self.validation_errors,
61+
"successful": self.is_successful(),
62+
"api_version": self.api_version
63+
}
64+
65+
66+
class ApiResponseDto(BaseDto):
67+
def __init__(self, payload=None):
68+
self.payload = payload
69+
self.service = ApiResponseServiceSection()
70+
71+
def to_dict(self):
72+
serialized_payload = self.payload
73+
if isinstance(self.payload, BaseDto):
74+
serialized_payload = self.payload.to_representation(self.payload)
75+
return {
76+
"payload": serialized_payload,
77+
"service": self.service.to_dict()
78+
}
79+
80+
81+
class RelatedDtoField(Field):
82+
def __init__(self, dto_class, required: bool = None, default=empty, initial=empty) -> None:
83+
super().__init__(read_only=False, write_only=False, source=None, required=required, default=default,
84+
initial=initial,
85+
label=None, help_text=None, style=None, error_messages=None, validators=None, allow_null=False)
86+
self.dto_class = dto_class
87+
88+
def to_representation(self, instance: BaseDto):
89+
return instance.to_dict()
90+
91+
def to_internal_value(self, data: dict):
92+
dto = self.dto_class.from_dict(data)
93+
dto.is_valid()
94+
return dto
95+
96+
97+
class PaginationOptions(object):
98+
""" Pagination options class, has offset and limit parameters."""
99+
100+
def __init__(self, offset: int = 0, limit: int = 50) -> None:
101+
""" Return pagination options object with limit and offset.
102+
:param offset: Pagination offset
103+
:type offset: int
104+
:param limit: Pagination limit
105+
:type limit: int
106+
:rtype: PaginationOptions
107+
"""
108+
self.offset = offset
109+
self.limit = limit

‎api_commons/error.py

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
class InvalidInputDataError(Exception):
2+
def __init__(self, msg, input_value, *args, **kwargs):
3+
super().__init__(msg, *args, **kwargs)
4+
self.input_value = input_value
5+
6+
7+
class InvalidPaginationOptionsError(Exception):
8+
pass
9+
10+
11+
class NotFoundException(Exception):
12+
def __init__(self, msg, input_value, *args, **kwargs):
13+
super().__init__(msg, *args, **kwargs)
14+
self.input_value = input_value
15+
16+
17+
class ErrorCode:
18+
def __init__(self, error_code, dto_or_error_message=''):
19+
self.error_code = error_code
20+
self.dto_or_error_message = dto_or_error_message

‎development/copyright-update

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash
2+
3+
cd $(dirname "$(realpath "$0")")
4+
copyright -c ./copyright.json ../api_commons

‎development/copyright.json

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"author": "Dmitry Berezovsky",
3+
"back": false,
4+
"exclude": "",
5+
"include": "*.py",
6+
"license": "mit",
7+
"program": "Django API Commons",
8+
"short": "Boilerplate commons for django based web api application.",
9+
"single": true,
10+
"year": "2017"
11+
}

‎example_service/__init__.py

Whitespace-only changes.

‎example_service/api/__init__.py

Whitespace-only changes.

‎example_service/api/calculator/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from django.http import HttpRequest
2+
3+
from example_service.api import errors
4+
from api_commons.common import ApiResponse
5+
from example_service.api.calculator.dto import CalculationRequestDto, CalculationResultDto
6+
from example_service.api.common import ExampleController
7+
from example_service.core.services import CalculationService
8+
9+
10+
class SumController(ExampleController):
11+
def get(self, request: HttpRequest):
12+
return ApiResponse.not_found(errors.NOT_SUPPORTED)
13+
14+
def post(self, request: HttpRequest):
15+
dto = CalculationRequestDto.from_dict(request.data)
16+
if not dto.is_valid():
17+
return ApiResponse.bad_request(dto)
18+
task = CalculationService.CalculationTask(arg1=dto.arg1, arg2=dto.arg2, operator=dto.operator)
19+
try:
20+
result = CalculationService.calculate(task)
21+
return ApiResponse.success(
22+
CalculationResultDto.from_calculation_result(result)
23+
)
24+
except Exception as e: # Should be more concrete
25+
return ApiResponse.bad_request("Unable to calculate your expression: " + str(e))

‎example_service/api/calculator/dto.py

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from rest_framework import serializers
2+
3+
from api_commons.dto import BaseDto
4+
from example_service.core.services import CalculationService
5+
from example_service.core.services.CalculationService import CalculationResult
6+
7+
8+
class CalculationRequestDto(BaseDto):
9+
arg1 = serializers.DecimalField(required=True, max_digits=5, decimal_places=2)
10+
arg2 = serializers.DecimalField(required=False, max_digits=5, decimal_places=2)
11+
operator = serializers.ChoiceField(required=True, choices=CalculationService.Operator.choices)
12+
13+
14+
class CalculationResultDto(BaseDto):
15+
result = serializers.DecimalField(required=False, max_digits=5, decimal_places=2)
16+
result_int = serializers.IntegerField(required=False)
17+
result_str = serializers.IntegerField(required=False)
18+
19+
@classmethod
20+
def from_calculation_result(cls, calculation_result: CalculationResult):
21+
dto = cls()
22+
dto.result = calculation_result.answer
23+
dto.result_int = int(calculation_result.answer)
24+
dto.result_str = str(calculation_result.answer)
25+
return dto
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from django.conf.urls import url
2+
3+
from .controllers import *
4+
5+
urlpatterns = [
6+
url(r'^$', SumController.as_view()),
7+
]

‎example_service/api/common.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from api_commons.common import BaseController
2+
3+
4+
class ExampleController(BaseController):
5+
pass

‎example_service/api/errors.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from api_commons.error import ErrorCode
2+
3+
INCORRECT_CREDENTIALS = ErrorCode(0x001, "Incorrect credentials")
4+
NOT_SUPPORTED = ErrorCode(0x002, "Not supported")
5+
CALCULATION_FAILED = ErrorCode(0x003, "Calculation failed")

‎example_service/api/ping/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from api_commons.common import ApiResponse
2+
from example_service.api.common import ExampleController
3+
4+
5+
class PingController(ExampleController):
6+
def get(self, request):
7+
return ApiResponse.success()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .PingController import PingController

‎example_service/api/ping/urls.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from django.conf.urls import url
2+
3+
from .controllers import *
4+
5+
urlpatterns = [
6+
url(r'^$', PingController.as_view()),
7+
]

‎example_service/api/urls.py

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from django.conf.urls import url, include
2+
3+
import api_commons.common
4+
5+
urlpatterns = [
6+
url(r'^ping/?', include('api.ping.urls')),
7+
url(r'^calc/?', include('api.calculator.urls')),
8+
]
9+
10+
handler404 = api_commons.common.error_404_handler
11+
handler500 = api_commons.common.error_500_handler

‎example_service/core/__init__.py

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
class Operator:
2+
PLUS = '+'
3+
MINUS = '+'
4+
MUL = '*'
5+
DIV = '/'
6+
7+
choices = (
8+
(PLUS, '+'),
9+
(MINUS, '-'),
10+
(MUL, '*'),
11+
(DIV, '/'),
12+
)
13+
14+
15+
# Should be model
16+
class CalculationTask:
17+
def __init__(self, arg1: float = None, operator: Operator = None, arg2: float = None):
18+
super().__init__()
19+
self.argument1 = arg1
20+
self.argument2 = arg2
21+
self.operator = operator
22+
23+
24+
# Should be model
25+
class CalculationResult:
26+
def __init__(self, answer: float = None):
27+
super().__init__()
28+
self.answer = answer
29+
30+
31+
def calculate(task: CalculationTask) -> CalculationResult:
32+
return CalculationResult(4)

‎example_service/core/services/__init__.py

Whitespace-only changes.

‎example_service/env/__init__.py

Whitespace-only changes.

‎example_service/env/dev.py

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from example_service.settings.base import *
2+
import os
3+
4+
DEBUG = os.environ.get('DEBUG', '0') in ['1', 'true', 'True', 'yes', 'Yes']
5+
6+
DEFAULT_LOG_LEVEL = 'DEBUG'
7+
8+
LOGGING = {
9+
'version': 1,
10+
'disable_existing_loggers': False,
11+
'filters': {
12+
'require_debug_true': {
13+
'()': 'django.utils.log.RequireDebugTrue',
14+
}
15+
},
16+
'handlers': {
17+
'console': {
18+
'level': DEFAULT_LOG_LEVEL,
19+
'filters': [],
20+
'class': 'logging.StreamHandler',
21+
}
22+
},
23+
'loggers': {
24+
# 'django.db.backends': {
25+
# 'level': 'DEBUG',
26+
# 'handlers': ['console'],
27+
# },
28+
'root': {
29+
'handlers': ['console', ],
30+
'level': DEFAULT_LOG_LEVEL,
31+
},
32+
'django.request': {
33+
'handlers': ['console', ],
34+
'level': 'ERROR',
35+
'propagate': False,
36+
},
37+
'django': {
38+
'handlers': ['console', ],
39+
'propagate': True,
40+
'level': DEFAULT_LOG_LEVEL,
41+
},
42+
}
43+
}
44+
45+
ALLOWED_HOSTS = ['*']

‎example_service/manage.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env python
2+
import os
3+
import sys
4+
5+
if __name__ == "__main__":
6+
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
7+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", 'env.' + os.environ.get('ENV', 'dev'))
8+
9+
try:
10+
from django.core.management import execute_from_command_line
11+
except ImportError:
12+
# The above import may fail for some other reason. Ensure that the
13+
# issue is really that Django is missing to avoid masking other
14+
# exceptions on Python 2.
15+
try:
16+
import django
17+
except ImportError:
18+
raise ImportError(
19+
"Couldn't import Django. Are you sure it's installed and "
20+
"available on your PYTHONPATH environment variable? Did you "
21+
"forget to activate a virtual environment?"
22+
)
23+
raise
24+
execute_from_command_line(sys.argv)

‎example_service/settings/__init__.py

Whitespace-only changes.
+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
INSTALLED_APPS = [
2+
'django.contrib.auth',
3+
'django.contrib.contenttypes',
4+
'django.contrib.sessions',
5+
'django.contrib.messages',
6+
'rest_framework',
7+
# Own components
8+
'api',
9+
]
10+
MIDDLEWARE = [
11+
'django.middleware.security.SecurityMiddleware',
12+
'django.contrib.sessions.middleware.SessionMiddleware',
13+
'django.middleware.common.CommonMiddleware',
14+
'django.contrib.auth.middleware.AuthenticationMiddleware',
15+
'django.contrib.messages.middleware.MessageMiddleware',
16+
]
17+
REST_FRAMEWORK = {
18+
'EXCEPTION_HANDLER': 'api_commons.exception_handler',
19+
}
20+
ROOT_URLCONF = 'api.urls'
21+
22+
AUTH_PASSWORD_VALIDATORS = [
23+
{
24+
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
25+
},
26+
{
27+
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
28+
},
29+
{
30+
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
31+
},
32+
{
33+
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
34+
},
35+
]

‎example_service/settings/base.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import socket
2+
3+
import os
4+
5+
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
6+
7+
VERSION = "1.0.0"
8+
API_VERSION = 1
9+
10+
LANGUAGE_CODE = 'en-us'
11+
TIME_ZONE = 'UTC'
12+
USE_I18N = True
13+
USE_L10N = True
14+
USE_TZ = True
15+
16+
# SECURITY WARNING: keep the secret key used in production secret!
17+
SECRET_KEY = 'lkjt534ofdpsafls356u45ijtkifjs;alfskloxchok6jfhs'
18+
DEBUG = False
19+
ALLOWED_HOSTS = []
20+
21+
try:
22+
HOSTNAME = socket.gethostname()
23+
except:
24+
HOSTNAME = 'localhost'
25+
26+
from .app_structure import *

‎example_service/urls.py

Whitespace-only changes.

‎requirements.txt

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.idea
2+
Django==1.11.2
3+
djangorestframework==3.5.4
4+
typing==3.5.3.0

0 commit comments

Comments
 (0)
Please sign in to comment.