├── .gitignore ├── README.md ├── api ├── __init__.py ├── v10 │ ├── __init__.py │ ├── urls.py │ └── views.py └── v11 │ ├── __init__.py │ ├── charts.py │ ├── common.py │ ├── drawingTemplates.py │ ├── studyTemplates.py │ └── urls.py ├── charting_library_charts ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py ├── manage.py ├── model ├── __init__.py ├── migrations │ ├── 0001_initial.py │ ├── 0002_auto_20141007_1601.py │ ├── 0003_auto_20141008_1252.py │ ├── 0004_studytemplate.py │ ├── 0005_chart_indexes.py │ ├── 0006_study_templates_indexes.py │ ├── 0007_drawingtemplate.py │ ├── 0008_drawingtemplate_tool.py │ └── __init__.py └── models.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pyo 3 | *sublime* 4 | *\.vscode* 5 | python_environment/* 6 | environment/* 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Advanced Charts Save and Load Storage Example 2 | ================ 3 | 4 | This is a backend implementing a chart storage with Python and PostgreSQL. 5 | You can run this storage on your server to process users' saved data such as [chart layouts], [drawing templates], and [indicator templates]. 6 | For more information, refer to the [Saving and loading charts] section of the Advanced Charts documentation. 7 | 8 | ## Requirements 9 | 10 | Python 3x, pip, Django, PostgreSQL 11 | 12 | ## How to start 13 | 14 | 1. Clone the repository to your host machine. 15 | 2. Install Python 3.x and pip. Use a virtual environment if your host has an older Python version that cannot be upgraded. 16 | 3. Install PostgreSQL or any other Django-friendly database engine. You can also install pgAdmin or any other administrative tool for your database. 17 | 4. Go to your chart storage folder and install the required dependencies: `pip install -r requirements.txt`. For Unix users: you should have the python-dev package to install `psycopg2`. 18 | 5. Create an empty database in PostgreSQL using either the command line or pgAdmin. 19 | 6. Configure your database connection in `charting_library_charts/settings.py` (see `DATABASES` at [line 16]). 20 | 7. Run `python manage.py migrate` to create the database schema without any data. 21 | 8. Generate a secret key by running `python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'`. 22 | 9. Set your secret key to your environment variables `export SECRET_KEY='...'`. 23 | 10. Start a test instance of your database by running `python manage.py runserver`. Note that for production environments, you should avoid using `runserver` and instead use a suitable WSGI (Web Server Gateway Interface) server like Gunicorn. 24 | 11. Set [`charts_storage_url`] in the [Widget Constructor] to the URL of your chart storage. Additionally, ensure to set [`client_id` and `user_id`]. 25 | 26 | [chart layouts]: https://www.tradingview.com/charting-library-docs/latest/saving_loading/#chart-layouts 27 | [`charts_storage_url`]: https://www.tradingview.com/charting-library-docs/latest/api/interfaces/Charting_Library.ChartingLibraryWidgetOptions/#charts_storage_url 28 | [`client_id` and `user_id`]: https://www.tradingview.com/charting-library-docs/latest/saving_loading/save-load-rest-api/#manage-access-to-saved-charts 29 | [drawing templates]: https://www.tradingview.com/charting-library-docs/latest/saving_loading/#drawing-templates 30 | [indicator templates]: https://www.tradingview.com/charting-library-docs/latest/saving_loading/#indicator-templates 31 | [line 16]: https://github.com/tradingview/saveload_backend/blob/master/charting_library_charts/settings.py#L16 32 | [Saving and loading charts]: https://www.tradingview.com/charting-library-docs/latest/saving_loading/ 33 | [Widget Constructor]: https://www.tradingview.com/charting-library-docs/latest/core_concepts/Widget-Constructor/ 34 | -------------------------------------------------------------------------------- /api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tradingview/saveload_backend/2ae61582fc0e6fade43542df792b763646eec3d3/api/__init__.py -------------------------------------------------------------------------------- /api/v10/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tradingview/saveload_backend/2ae61582fc0e6fade43542df792b763646eec3d3/api/v10/__init__.py -------------------------------------------------------------------------------- /api/v10/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from . import views 3 | from django.views.decorators.csrf import csrf_exempt 4 | 5 | #GET charts?userid=0&clientid=1 6 | #GET charts?userid=0&clientid=1&chartid=2 7 | #DELETE charts?userid=0&clientid=1&chartid=2 8 | #POST charts?userid=0&clientid=1&chartid=2 9 | #POST charts?userid=0&clientid=1 10 | 11 | urlpatterns = [ 12 | url(r'^charts$', csrf_exempt(views.doTheMagic)), 13 | ] 14 | -------------------------------------------------------------------------------- /api/v10/views.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | from model import models 3 | import json 4 | from datetime import datetime 5 | import time 6 | 7 | 8 | def response(content): 9 | result = HttpResponse(content) 10 | result["Access-Control-Allow-Origin"] = "*" 11 | result["Access-Control-Allow-Methods"] = "GET, POST, DELETE, OPTIONS" 12 | result["Content-Type"] = "application/json" 13 | return result 14 | 15 | 16 | def error(text): 17 | return response(json.dumps({'status': 'error','message': text})) 18 | 19 | 20 | def doTheMagic(request): 21 | clientId = request.GET.get('client', '') 22 | 23 | if (clientId == ''): 24 | return error('Wrong client id') 25 | 26 | userId = request.GET.get('user', '') 27 | 28 | if (userId == ''): 29 | return error('Wrong user id') 30 | 31 | chartId = request.GET.get('chart', '') 32 | 33 | if request.method == 'OPTIONS': 34 | return respondToOptionsRequest(request.META) 35 | 36 | if request.method == 'GET': 37 | if chartId == '': 38 | return getAllUserCharts(clientId, userId) 39 | else: 40 | return getChartContent(clientId, userId, chartId) 41 | 42 | elif request.method == 'DELETE': 43 | if chartId == '': 44 | return error('Wrong chart id') 45 | else: 46 | return removeChart(clientId, userId, chartId) 47 | 48 | elif request.method == 'POST': 49 | chartName = request.POST.get('name') 50 | symbol = request.POST.get('symbol') 51 | resolution = request.POST.get('resolution') 52 | content = request.POST.get('content') 53 | if chartId == '': 54 | return saveChart(clientId, userId, chartName, symbol, resolution, content) 55 | else: 56 | return rewriteChart(clientId, userId, chartId, chartName, symbol, resolution, content) 57 | 58 | else: 59 | return error('Wrong request') 60 | 61 | 62 | def respondToOptionsRequest(requestHeaders): 63 | result = response(json.dumps({'status': "ok"})) 64 | result["Access-Control-Allow-Headers"] = requestHeaders["HTTP_ACCESS_CONTROL_REQUEST_HEADERS"] 65 | return result 66 | 67 | 68 | def getAllUserCharts(clientId, userId): 69 | chartsList = models.Chart.objects.defer('content').filter(ownerSource = clientId, ownerId = userId) 70 | result = map(lambda x : {'id': x.id, 'name': x.name, 'timestamp': time.mktime(x.lastModified.timetuple()), 'symbol': x.symbol, 'resolution': x.resolution} , chartsList) 71 | return response(json.dumps({'status': "ok", 'data': list(result)})) 72 | 73 | 74 | def getChartContent(clientId, userId, chartId): 75 | try: 76 | chart = models.Chart.objects.get(ownerSource = clientId, ownerId = userId, id = chartId) 77 | result = json.dumps({'status': 'ok', 'data': { 'id': chart.id, 'name': chart.name, 'timestamp': time.mktime(chart.lastModified.timetuple()), 'content': chart.content}}) 78 | return response(result) 79 | except: 80 | return error('Chart not found') 81 | 82 | 83 | def removeChart(clientId, userId, chartId): 84 | try: 85 | chart = models.Chart.objects.get(ownerSource = clientId, ownerId = userId, id = chartId) 86 | chart.delete() 87 | return response(json.dumps({'status': 'ok'})) 88 | except: 89 | return error('Chart not found') 90 | 91 | 92 | def saveChart(clientId, userId, chartName, symbol, resolution, content): 93 | newChart = models.Chart( 94 | ownerSource = clientId, 95 | ownerId = userId, 96 | name = chartName, 97 | content = content, 98 | lastModified = datetime.now(), 99 | symbol = symbol, 100 | resolution = resolution 101 | ) 102 | 103 | newChart.save() 104 | return response(json.dumps({'status': 'ok', 'id': newChart.id})) 105 | 106 | 107 | def rewriteChart(clientId, userId, chartId, chartName, symbol, resolution, content): 108 | try: 109 | chart = models.Chart.objects.get(ownerSource = clientId, ownerId = userId, id = chartId) 110 | chart.lastModified = datetime.utcnow() 111 | chart.content = content 112 | chart.name = chartName 113 | chart.symbol = symbol 114 | chart.resolution = resolution 115 | 116 | chart.save() 117 | return response(json.dumps({'status': 'ok'})) 118 | except: 119 | return error('Chart not found') 120 | -------------------------------------------------------------------------------- /api/v11/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tradingview/saveload_backend/2ae61582fc0e6fade43542df792b763646eec3d3/api/v11/__init__.py -------------------------------------------------------------------------------- /api/v11/charts.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | import json 3 | import time 4 | from datetime import datetime 5 | 6 | from model import models 7 | from api.v11 import common 8 | 9 | 10 | def processRequest(request): 11 | parsedRequest = common.parseRequest(request) 12 | 13 | if parsedRequest['error'] is not None: 14 | return parsedRequest['error'] 15 | 16 | if parsedRequest['response'] is not None: 17 | return parsedRequest['response'] 18 | 19 | 20 | clientId = parsedRequest["clientId"] 21 | userId = parsedRequest['userId'] 22 | chartId = request.GET.get('chart', '') 23 | 24 | 25 | if request.method == 'GET': 26 | if chartId == '': 27 | return getAllUserCharts(clientId, userId) 28 | else: 29 | return getChartContent(clientId, userId, chartId) 30 | 31 | elif request.method == 'DELETE': 32 | if chartId == '': 33 | return common.error('Wrong chart id') 34 | else: 35 | return removeChart(clientId, userId, chartId) 36 | 37 | elif request.method == 'POST': 38 | chartName = request.POST.get('name') 39 | symbol = request.POST.get('symbol') 40 | resolution = request.POST.get('resolution') 41 | content = request.POST.get('content') 42 | if chartId == '': 43 | return saveChart(clientId, userId, chartName, symbol, resolution, content) 44 | else: 45 | return rewriteChart(clientId, userId, chartId, chartName, symbol, resolution, content) 46 | 47 | else: 48 | return common.error('Wrong request') 49 | 50 | 51 | #----------------------------------------------------------------------------------------------------- 52 | #----------------------------------------------------------------------------------------------------- 53 | #----------------------------------------------------------------------------------------------------- 54 | 55 | 56 | def getAllUserCharts(clientId, userId): 57 | chartsList = models.Chart.objects.defer('content').filter(ownerSource = clientId, ownerId = userId) 58 | result = map(lambda x : {'id': x.id, 'name': x.name, 'timestamp': time.mktime(x.lastModified.timetuple()), 'symbol': x.symbol, 'resolution': x.resolution} , chartsList) 59 | return common.response(json.dumps({'status': "ok", 'data': list(result)})) 60 | 61 | 62 | def getChartContent(clientId, userId, chartId): 63 | try: 64 | chart = models.Chart.objects.get(ownerSource = clientId, ownerId = userId, id = chartId) 65 | result = json.dumps({'status': 'ok', 'data': { 'id': chart.id, 'name': chart.name, 'timestamp': time.mktime(chart.lastModified.timetuple()), 'content': chart.content}}) 66 | return common.response(result) 67 | except: 68 | return common.error('Chart not found') 69 | 70 | 71 | def removeChart(clientId, userId, chartId): 72 | try: 73 | chart = models.Chart.objects.get(ownerSource = clientId, ownerId = userId, id = chartId) 74 | chart.delete() 75 | return common.response(json.dumps({'status': 'ok'})) 76 | except: 77 | return common.error('Chart not found') 78 | 79 | 80 | def saveChart(clientId, userId, chartName, symbol, resolution, content): 81 | newChart = models.Chart( 82 | ownerSource = clientId, 83 | ownerId = userId, 84 | name = chartName, 85 | content = content, 86 | lastModified = datetime.now(), 87 | symbol = symbol, 88 | resolution = resolution 89 | ) 90 | 91 | newChart.save() 92 | return common.response(json.dumps({'status': 'ok', 'id': newChart.id})) 93 | 94 | 95 | def rewriteChart(clientId, userId, chartId, chartName, symbol, resolution, content): 96 | try: 97 | chart = models.Chart.objects.get(ownerSource = clientId, ownerId = userId, id = chartId) 98 | chart.lastModified = datetime.now() 99 | chart.content = content 100 | chart.name = chartName 101 | chart.symbol = symbol 102 | chart.resolution = resolution 103 | 104 | chart.save() 105 | return common.response(json.dumps({'status': 'ok'})) 106 | except: 107 | return common.error('Chart not found') 108 | -------------------------------------------------------------------------------- /api/v11/common.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | import json 3 | 4 | 5 | def response(content): 6 | result = HttpResponse(content) 7 | result["Access-Control-Allow-Origin"] = "*" 8 | result["Access-Control-Allow-Methods"] = "GET, POST, DELETE, OPTIONS" 9 | result["Content-Type"] = "application/json" 10 | return result 11 | 12 | 13 | def error(text): 14 | return response(json.dumps({'status': 'error','message': text})) 15 | 16 | 17 | def respondToOptionsRequest(requestHeaders): 18 | result = response(json.dumps({'status': "ok"})) 19 | if "HTTP_ACCESS_CONTROL_REQUEST_HEADERS" in requestHeaders: 20 | result["Access-Control-Allow-Headers"] = requestHeaders["HTTP_ACCESS_CONTROL_REQUEST_HEADERS"] 21 | else: 22 | result["Access-Control-Allow-Headers"] = "*" 23 | 24 | return result 25 | 26 | 27 | def parseRequest(request): 28 | clientId = request.GET.get('client', '') 29 | userId = request.GET.get('user', '') 30 | 31 | err = None 32 | response = None 33 | 34 | if (clientId == ''): 35 | err = error('Wrong client id') 36 | elif (userId == ''): 37 | err = error('Wrong user id') 38 | elif request.method == 'OPTIONS': 39 | response = respondToOptionsRequest(request.META) 40 | 41 | return { 42 | "error": err, 43 | "response": response, 44 | "clientId": clientId, 45 | "userId": userId 46 | } 47 | -------------------------------------------------------------------------------- /api/v11/drawingTemplates.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | import json 3 | import time 4 | from datetime import datetime 5 | 6 | from model import models 7 | from api.v11 import common 8 | 9 | 10 | def processRequest(request): 11 | parsedRequest = common.parseRequest(request) 12 | 13 | if parsedRequest['error'] is not None: 14 | return parsedRequest['error'] 15 | 16 | if parsedRequest['response'] is not None: 17 | return parsedRequest['response'] 18 | 19 | clientId = parsedRequest["clientId"] 20 | userId = parsedRequest['userId'] 21 | name = request.GET.get('name', '') 22 | tool = request.GET.get('tool', '') 23 | 24 | if request.method == 'GET': 25 | if name == '': 26 | return getTemplates(clientId, userId, tool) 27 | else: 28 | return getTemplate(clientId, userId, tool, name) 29 | 30 | elif request.method == 'POST': 31 | content = request.POST.get('content', '') 32 | return createOrUpdateTemplate(clientId, userId, name, tool, content) 33 | 34 | elif request.method == 'DELETE': 35 | return removeTemplate(clientId, userId, tool, name) 36 | 37 | else: 38 | return common.error('Wrong request') 39 | 40 | 41 | #----------------------------------------------------------------------------------------------------- 42 | #----------------------------------------------------------------------------------------------------- 43 | #----------------------------------------------------------------------------------------------------- 44 | 45 | 46 | def getTemplates(clientId, userId, tool): 47 | try: 48 | items = models.DrawingTemplate.objects.defer('content').filter(ownerSource = clientId, ownerId = userId, tool = tool) 49 | result = map(lambda x : x.name, items) 50 | return common.response(json.dumps({'status': "ok", 'data': list(result)})) 51 | except: 52 | return common.error('Error loading Drawing Templates') 53 | 54 | 55 | def getTemplate(clientId, userId, tool, name): 56 | try: 57 | item = models.DrawingTemplate.objects.get(ownerSource = clientId, ownerId = userId, tool = tool, name = name) 58 | result = json.dumps({'status': 'ok', 'data': { 'name': item.name, 'content': item.content}}) 59 | return common.response(result) 60 | except: 61 | return common.error('Drawing Template not found') 62 | 63 | 64 | def removeTemplate(clientId, userId, tool, name): 65 | try: 66 | item = models.DrawingTemplate.objects.get(ownerSource = clientId, ownerId = userId, tool = tool, name = name) 67 | item.delete() 68 | return common.response(json.dumps({'status': 'ok'})) 69 | except: 70 | return common.error('Drawing Template not found') 71 | 72 | 73 | def createOrUpdateTemplate(clientId, userId, name, tool, content): 74 | if not content: 75 | return common.error('No content to save') 76 | 77 | if not name: 78 | return common.error('Name of template should not be empty') 79 | 80 | try: 81 | newItem, created = models.DrawingTemplate.objects.get_or_create(ownerSource=clientId, ownerId=userId, name=name, tool=tool) 82 | newItem.content = content 83 | newItem.save() 84 | return common.response(json.dumps({'status': 'ok'})) 85 | except: 86 | return common.error('Error updating Drawing Template') 87 | -------------------------------------------------------------------------------- /api/v11/studyTemplates.py: -------------------------------------------------------------------------------- 1 | from django.http import HttpResponse 2 | import json 3 | import time 4 | from datetime import datetime 5 | 6 | from model import models 7 | from api.v11 import common 8 | 9 | 10 | def processRequest(request): 11 | parsedRequest = common.parseRequest(request) 12 | 13 | if parsedRequest['error'] is not None: 14 | return parsedRequest['error'] 15 | 16 | if parsedRequest['response'] is not None: 17 | return parsedRequest['response'] 18 | 19 | clientId = parsedRequest["clientId"] 20 | userId = parsedRequest['userId'] 21 | templateName = request.GET.get('template', '') 22 | 23 | 24 | if request.method == 'GET': 25 | if templateName == '': 26 | return getAllTemplatesList(clientId, userId) 27 | else: 28 | return getTemplate(clientId, userId, templateName) 29 | 30 | elif request.method == 'DELETE': 31 | return removeTemplate(clientId, userId, templateName) 32 | 33 | elif request.method == 'POST': 34 | templateName = request.POST.get('name') 35 | content = request.POST.get('content') 36 | return createOrUpdateTemplate(clientId, userId, templateName, content) 37 | 38 | else: 39 | return common.error('Wrong request') 40 | 41 | 42 | #----------------------------------------------------------------------------------------------------- 43 | #----------------------------------------------------------------------------------------------------- 44 | #----------------------------------------------------------------------------------------------------- 45 | 46 | 47 | def getAllTemplatesList(clientId, userId): 48 | items = models.StudyTemplate.objects.defer('content').filter(ownerSource = clientId, ownerId = userId) 49 | result = map(lambda x : {'name': x.name} , items) 50 | return common.response(json.dumps({'status': "ok", 'data': list(result)})) 51 | 52 | 53 | def getTemplate(clientId, userId, name): 54 | try: 55 | item = models.StudyTemplate.objects.get(ownerSource = clientId, ownerId = userId, name = name) 56 | result = json.dumps({'status': 'ok', 'data': { 'name': item.name, 'content': item.content}}) 57 | return common.response(result) 58 | except: 59 | return common.error('StudyTemplate not found') 60 | 61 | 62 | def removeTemplate(clientId, userId, name): 63 | try: 64 | item = models.StudyTemplate.objects.get(ownerSource = clientId, ownerId = userId, name = name) 65 | item.delete() 66 | return common.response(json.dumps({'status': 'ok'})) 67 | except: 68 | return common.error('StudyTemplate not found') 69 | 70 | 71 | def createOrUpdateTemplate(clientId, userId, name, content): 72 | if not content: 73 | return common.error('No content to save') 74 | 75 | if not name: 76 | return common.error('Name of template should not be empty') 77 | 78 | try: 79 | newItem, created = models.StudyTemplate.objects.get_or_create(ownerSource=clientId, ownerId=userId, name=name) 80 | 81 | newItem.content = content 82 | newItem.save() 83 | 84 | return common.response(json.dumps({'status': 'ok'})) 85 | except: 86 | return common.error('Error updating Study Template') 87 | -------------------------------------------------------------------------------- /api/v11/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | from api.v11 import studyTemplates 3 | from api.v11 import drawingTemplates 4 | from api.v11 import charts 5 | from django.views.decorators.csrf import csrf_exempt 6 | 7 | 8 | urlpatterns = [ 9 | url(r'^charts$', csrf_exempt(charts.processRequest)), 10 | url(r'^study_templates$', csrf_exempt(studyTemplates.processRequest)), 11 | url(r'^drawing_templates$', csrf_exempt(drawingTemplates.processRequest)), 12 | ] 13 | -------------------------------------------------------------------------------- /charting_library_charts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tradingview/saveload_backend/2ae61582fc0e6fade43542df792b763646eec3d3/charting_library_charts/__init__.py -------------------------------------------------------------------------------- /charting_library_charts/settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for charting_library_charts project. 2 | 3 | import os 4 | 5 | DEBUG = False 6 | TEMPLATE_DEBUG = DEBUG 7 | 8 | ALLOWED_HOSTS = ['*'] 9 | 10 | ADMINS = ( 11 | # ('Your Name', 'your_email@example.com'), 12 | ) 13 | 14 | MANAGERS = ADMINS 15 | 16 | DATABASES = { 17 | 'default': { 18 | 'ENGINE': 'django.db.backends.postgresql_psycopg2', 19 | 'NAME': os.getenv('DB_NAME', 'charting_library'), 20 | 'USER': os.getenv('DB_USER', 'postgres'), 21 | 'PASSWORD': os.getenv('DB_PASSWORD', 'postgres'), 22 | 'HOST': os.getenv('DB_HOST', 'localhost'), 23 | 'PORT': int(os.getenv('DB_PORT', '5432')), 24 | } 25 | } 26 | 27 | 28 | # Local time zone for this installation. Choices can be found here: 29 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 30 | # although not all choices may be available on all operating systems. 31 | # In a Windows environment this must be set to your system time zone. 32 | TIME_ZONE = 'Etc/UTC' 33 | 34 | # Language code for this installation. All choices can be found here: 35 | # http://www.i18nguy.com/unicode/language-identifiers.html 36 | LANGUAGE_CODE = 'en-us' 37 | 38 | SITE_ID = 1 39 | 40 | # If you set this to False, Django will make some optimizations so as not 41 | # to load the internationalization machinery. 42 | USE_I18N = True 43 | 44 | # If you set this to False, Django will not format dates, numbers and 45 | # calendars according to the current locale. 46 | USE_L10N = True 47 | 48 | # If you set this to False, Django will not use timezone-aware datetimes. 49 | USE_TZ = True 50 | 51 | # Absolute filesystem path to the directory that will hold user-uploaded files. 52 | # Example: "/home/media/media.lawrence.com/media/" 53 | MEDIA_ROOT = '' 54 | 55 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 56 | # trailing slash. 57 | # Examples: "http://media.lawrence.com/media/", "http://example.com/media/" 58 | MEDIA_URL = '' 59 | 60 | # Absolute path to the directory static files should be collected to. 61 | # Don't put anything in this directory yourself; store your static files 62 | # in apps' "static/" subdirectories and in STATICFILES_DIRS. 63 | # Example: "/home/media/media.lawrence.com/static/" 64 | STATIC_ROOT = '' 65 | 66 | # URL prefix for static files. 67 | # Example: "http://media.lawrence.com/static/" 68 | STATIC_URL = '/static/' 69 | 70 | # Additional locations of static files 71 | STATICFILES_DIRS = ( 72 | # Put strings here, like "/home/html/static" or "C:/www/django/static". 73 | # Always use forward slashes, even on Windows. 74 | # Don't forget to use absolute paths, not relative paths. 75 | ) 76 | 77 | # List of finder classes that know how to find static files in 78 | # various locations. 79 | STATICFILES_FINDERS = ( 80 | 'django.contrib.staticfiles.finders.FileSystemFinder', 81 | 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 82 | # 'django.contrib.staticfiles.finders.DefaultStorageFinder', 83 | ) 84 | 85 | # Make this unique, and don't share it with anybody. 86 | SECRET_KEY = os.getenv('SECRET_KEY', 'c5-%u0l1*(dj@u33!5*o_gtti0)kkh$m%i#@!(!$d%779kaa&b') 87 | 88 | # List of callables that know how to import templates from various sources. 89 | TEMPLATE_LOADERS = ( 90 | 'django.template.loaders.filesystem.Loader', 91 | 'django.template.loaders.app_directories.Loader', 92 | # 'django.template.loaders.eggs.Loader', 93 | ) 94 | 95 | MIDDLEWARE_CLASSES = ( 96 | 'django.middleware.common.CommonMiddleware', 97 | 'django.contrib.sessions.middleware.SessionMiddleware', 98 | 'django.middleware.csrf.CsrfViewMiddleware', 99 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 100 | 'django.contrib.messages.middleware.MessageMiddleware', 101 | # Uncomment the next line for simple clickjacking protection: 102 | # 'django.middleware.clickjacking.XFrameOptionsMiddleware', 103 | ) 104 | 105 | MIDDLEWARE = ( 106 | 'django_prometheus.middleware.PrometheusBeforeMiddleware', 107 | 'django_prometheus.middleware.PrometheusAfterMiddleware', 108 | ) 109 | 110 | ROOT_URLCONF = 'charting_library_charts.urls' 111 | 112 | # Python dotted path to the WSGI application used by Django's runserver. 113 | WSGI_APPLICATION = 'charting_library_charts.wsgi.application' 114 | 115 | TEMPLATE_DIRS = ( 116 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 117 | # Always use forward slashes, even on Windows. 118 | # Don't forget to use absolute paths, not relative paths. 119 | ) 120 | 121 | INSTALLED_APPS = ( 122 | 'django.contrib.auth', 123 | 'django.contrib.contenttypes', 124 | #'django.contrib.sessions', 125 | #'django.contrib.sites', 126 | #'django.contrib.messages', 127 | #'django.contrib.staticfiles', 128 | # Uncomment the next line to enable the admin: 129 | # 'django.contrib.admin', 130 | # Uncomment the next line to enable admin documentation: 131 | # 'django.contrib.admindocs', 132 | 'model', 133 | 'django_prometheus', 134 | ) 135 | 136 | # A sample logging configuration. The only tangible logging 137 | # performed by this configuration is to send an email to 138 | # the site admins on every HTTP 500 error when DEBUG=False. 139 | # See http://docs.djangoproject.com/en/dev/topics/logging for 140 | # more details on how to customize your logging configuration. 141 | LOGGING = { 142 | 'version': 1, 143 | 'disable_existing_loggers': False, 144 | 'filters': { 145 | 'require_debug_false': { 146 | '()': 'django.utils.log.RequireDebugFalse' 147 | } 148 | }, 149 | 'formatters': { 150 | 'verbose': { 151 | 'format': "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s", 152 | 'datefmt': "%d/%b/%Y %H:%M:%S" 153 | } 154 | }, 155 | 'handlers': { 156 | 'console': { 157 | 'class': 'logging.StreamHandler', 158 | 'formatter': 'verbose' 159 | }, 160 | 'mail_admins': { 161 | 'level': 'ERROR', 162 | 'filters': ['require_debug_false'], 163 | 'class': 'django.utils.log.AdminEmailHandler' 164 | } 165 | }, 166 | 'root': { 167 | 'handlers': ['console'], 168 | 'level': 'WARNING', 169 | }, 170 | 'loggers': { 171 | 'django.request': { 172 | 'handlers': ['mail_admins'], 173 | 'level': 'ERROR', 174 | 'propagate': True, 175 | }, 176 | 'py.warnings': { 177 | 'handlers': ['console'], 178 | 'level': 'WARNING', 179 | 'propagate': True, 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /charting_library_charts/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import include, url 2 | 3 | urlpatterns = [ 4 | url(r'^1\.0/', include('api.v10.urls')), 5 | url(r'^1\.1/', include('api.v11.urls')), 6 | url(r'', include('django_prometheus.urls')), 7 | ] 8 | -------------------------------------------------------------------------------- /charting_library_charts/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for charting_library_charts project. 3 | 4 | This module contains the WSGI application used by Django's development server 5 | and any production WSGI deployments. It should expose a module-level variable 6 | named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover 7 | this application via the ``WSGI_APPLICATION`` setting. 8 | 9 | Usually you will have the standard Django WSGI application here, but it also 10 | might make sense to replace the whole Django WSGI application with a custom one 11 | that later delegates to the Django one. For example, you could introduce WSGI 12 | middleware here, or combine a Django application with an application of another 13 | framework. 14 | 15 | """ 16 | import os 17 | 18 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "charting_library_charts.settings") 19 | 20 | # This application object is used by any WSGI server configured to use this 21 | # file. This includes Django's development server, if the WSGI_APPLICATION 22 | # setting points here. 23 | from django.core.wsgi import get_wsgi_application 24 | application = get_wsgi_application() 25 | 26 | # Apply WSGI middleware here. 27 | # from helloworld.wsgi import HelloWorldApplication 28 | # application = HelloWorldApplication(application) 29 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "charting_library_charts.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tradingview/saveload_backend/2ae61582fc0e6fade43542df792b763646eec3d3/model/__init__.py -------------------------------------------------------------------------------- /model/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import jsonfield.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Chart', 16 | fields=[ 17 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), 18 | ('ownerSource', models.CharField(max_length=200)), 19 | ('ownerId', models.CharField(max_length=200)), 20 | ('content', jsonfield.fields.JSONField()), 21 | ], 22 | options={ 23 | }, 24 | bases=(models.Model,), 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /model/migrations/0002_auto_20141007_1601.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import datetime 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('model', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='chart', 17 | name='lastModified', 18 | field=models.DateTimeField(default=datetime.date(2014, 10, 7), auto_now=True), 19 | preserve_default=False, 20 | ), 21 | migrations.AddField( 22 | model_name='chart', 23 | name='name', 24 | field=models.CharField(default='noname', max_length=200), 25 | preserve_default=False, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /model/migrations/0003_auto_20141008_1252.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('model', '0002_auto_20141007_1601'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='chart', 16 | name='resolution', 17 | field=models.CharField(default='D', max_length=10), 18 | preserve_default=False, 19 | ), 20 | migrations.AddField( 21 | model_name='chart', 22 | name='symbol', 23 | field=models.CharField(default='AA', max_length=50), 24 | preserve_default=False, 25 | ), 26 | migrations.AlterField( 27 | model_name='chart', 28 | name='lastModified', 29 | field=models.DateTimeField(), 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /model/migrations/0004_studytemplate.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals 3 | 4 | from django.db import models, migrations 5 | import jsonfield.fields 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('model', '0003_auto_20141008_1252'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='StudyTemplate', 17 | fields=[ 18 | ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), 19 | ('ownerSource', models.CharField(max_length=200)), 20 | ('ownerId', models.CharField(max_length=200)), 21 | ('name', models.CharField(max_length=200)), 22 | ('content', jsonfield.fields.JSONField()), 23 | ], 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /model/migrations/0005_chart_indexes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.27 on 2020-01-28 08:40 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('model', '0004_studytemplate'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='chart', 17 | name='ownerId', 18 | field=models.CharField(db_index=True, max_length=200), 19 | ), 20 | migrations.AlterField( 21 | model_name='chart', 22 | name='ownerSource', 23 | field=models.CharField(db_index=True, max_length=200), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /model/migrations/0006_study_templates_indexes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.27 on 2020-01-28 09:39 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('model', '0005_chart_indexes'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='studytemplate', 17 | name='ownerId', 18 | field=models.CharField(db_index=True, max_length=200), 19 | ), 20 | migrations.AlterField( 21 | model_name='studytemplate', 22 | name='ownerSource', 23 | field=models.CharField(db_index=True, max_length=200), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /model/migrations/0007_drawingtemplate.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.29 on 2020-07-14 09:28 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import jsonfield.fields 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('model', '0006_study_templates_indexes'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='DrawingTemplate', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('ownerSource', models.CharField(db_index=True, max_length=200)), 21 | ('ownerId', models.CharField(db_index=True, max_length=200)), 22 | ('name', models.CharField(max_length=200)), 23 | ('content', jsonfield.fields.JSONField()), 24 | ], 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /model/migrations/0008_drawingtemplate_tool.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.13 on 2020-07-15 19:25 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('model', '0007_drawingtemplate'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='drawingtemplate', 15 | name='tool', 16 | field=models.CharField(default='LineTool', max_length=200), 17 | preserve_default=False, 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /model/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tradingview/saveload_backend/2ae61582fc0e6fade43542df792b763646eec3d3/model/migrations/__init__.py -------------------------------------------------------------------------------- /model/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django_prometheus.models import ExportModelOperationsMixin 3 | from jsonfield import JSONField 4 | 5 | 6 | class Chart(ExportModelOperationsMixin('charts'), models.Model): 7 | ownerSource = models.CharField(max_length=200, db_index=True) 8 | ownerId = models.CharField(max_length=200, db_index=True) 9 | name = models.CharField(max_length=200) 10 | symbol = models.CharField(max_length=50) 11 | resolution = models.CharField(max_length=10) 12 | lastModified = models.DateTimeField() 13 | content = JSONField() 14 | 15 | def __str__(self): 16 | return self.ownerSource + ":" + self.ownerId 17 | 18 | def setContent(self, _content): 19 | self.content = _content 20 | 21 | 22 | class StudyTemplate(ExportModelOperationsMixin('study_template'), models.Model): 23 | ownerSource = models.CharField(max_length=200, db_index=True) 24 | ownerId = models.CharField(max_length=200, db_index=True) 25 | name = models.CharField(max_length=200) 26 | content = JSONField() 27 | 28 | def __str__(self): 29 | return self.ownerSource + ":" + self.ownerId 30 | 31 | def setContent(self, _content): 32 | self.content = _content 33 | 34 | 35 | class DrawingTemplate(ExportModelOperationsMixin('drawing_template'), models.Model): 36 | ownerSource = models.CharField(max_length=200, db_index=True) 37 | ownerId = models.CharField(max_length=200, db_index=True) 38 | name = models.CharField(max_length=200) 39 | tool = models.CharField(max_length=200) 40 | content = JSONField() 41 | 42 | def __str__(self): 43 | return self.ownerSource + ":" + self.ownerId 44 | 45 | def setContent(self, _content): 46 | self.content = _content 47 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==2.2.28 2 | psycopg2<2.9 3 | jsonfield==2.1.1 4 | django-prometheus==2.2.0 5 | --------------------------------------------------------------------------------