Они появляются при первоначальной миграции, при создании проекта Если их нету, то после первого пункта, запустите первоначальную миграцию в консоли:
> manage.py migrate
3) Нам понадобится форма авторизации (с регистрацией, если она нужна, можно проделать аналогичным образом) У меня пока нету времени на написание своей полноценной, поэтому возьму готовую форму из стандартных модулей админки django
Можно найти ее через поиск файлов по имени «login.html» в вашем проекте в папке дистрибутива django У меня, примерный путь был …/django/contrib/admin/templates/admin/login.html
Копируем файл полностью и вставляем уже в структуру ваших файлов проекта по пути:
/templates/registration/login.html
Это стандартный путь к такой форме. Если же вы решите разместить форму авторизации в другом месте, нужно указать путь к ней в конфигурацию settings.LOGIN_URL
4) Добавляем в основной файл роутинга urls.py информацию по форме авторизации
from django.contrib.auth import views as auth_views
...
urlpatterns = [
...
path('accounts/login/', auth_views.LoginView.as_view()),
]
5) В файле views.py для любого метода обработки запроса теперь может поставить декоратор, который будет требовать авторизацию Если пользователь уже авторизован — пускать запрос на дальнейшую обработку, если не был — показывать форму авторизации и после авторизации, возвращаться в целевой метод обработки запроса
from django.contrib.auth.decorators import login_required
...
@login_required
def my_view(request):
...
Есть сложный запрос, с большим количеством данных. Часто запрос моно описать моделями, чтобы усложнить задачу, будем писать сырой запрос mysql
Необходимо надежно выгрузить в CSV. Может даже миллионы строк
первой строкой в выгрузке будет хедер с названием столбцов
кастомный разделитель полей, например точка с запятой
1) views.py
добавляем необходимые импорты в начале кода
from django.http import StreamingHttpResponse
import csv
файло-подобный объект, который понадобится для добавления строк запроса в выходной CSV
class Echo:
def write(self, value):
return value
сам метод выгрузки в csv
def download_csv(filename, rows):
pseudo_buffer = Echo()
# используем кастомный разделитель полей - точка с запятой, по умолчанию, разделитель - запятая
writer = csv.writer(pseudo_buffer, delimiter=';')
return StreamingHttpResponse(
(
writer.writerow(row) for row in rows
),
content_type="text/csv",
headers={'Content-Disposition': 'attachment; filename="' + filename + '.csv"'},
)
и пример его использования:
def download_orders_for_user(request):
# например, запрос всех заказов пользователей с айдишниками от 1 до 10000 и суммой больше 10
query = """
SELECT order.number,
order.date,
order.sum,
order.status
FROM order
LEFT JOIN user ON user.id = order.user_id
WHERE user BETWEEN 1 and 10000
AND order.sum > 10
"""
with connections["default"].cursor() as cursor:
cursor.execute(query)
rows = cursor.fetchall()
# формируем имя файла
filename = 'orders_for_users'
# первой строкой у нас будет хедер с названием столбцов
header_row = (('Номер', 'Дата', 'Сумма', 'Статус'),)
rows = header_row + rows
return download_csv(filename, rows)
Это руководство для новичков в Django, которые прочитали начальное руководство по Django на официальном сайте и столкнулись с задачей загрузки csv файла с таблицей в базу данных
В этом случае, у вас есть приложение polls, с урлами, моделями, вьюшками для классов вопросов (Question) и ответов (Choices). И база данных с сгенерированными таблицами (polls_question, polls_choice)
Для примера, будем загружать в базу данных csv файл с вопросами (Questions).
Назовем файл questions_01a.csv и поместим туда такое содержимое:
Why?,2022-11-14 10:00
For what?,2022-11-14 10:01
When?,2022-11-14 10:02
Where?,2022-11-14 10:03
Перенос строк — как разделитель информации по вопросам, и запятая — как разделитель ячеек данных
Изменения в /polls/urls.py:
from django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
…
path('upload_csv/', views.upload_csv, name='upload_csv'),
]
Изменения в /polls/views.py:
from django.http import HttpResponseRedirect
from django.shortcuts import render, get_object_or_404
from django.urls import reverse
from django.views import generic
from django.utils import timezone
from django.contrib import messages
from .models import Question, Choice
…
def upload_csv(request):
data = {}
if "GET" == request.method:
return render(request, "polls/upload.html", data)
# if not GET, then proceed
try:
csv_file = request.FILES["csv_file"]
if not csv_file.name.endswith('.csv'):
messages.error(request, 'File is not a CSV')
return HttpResponseRedirect(reverse("polls:upload_csv"))
# if file is too large - error
if csv_file.multiple_chunks():
messages.error(request, "Uploaded file is too big (%.2f MB). " % (csv_file.size/(1000*1000),))
return HttpResponseRedirect(reverse("polls:upload_csv"))
file_data = csv_file.read().decode("utf-8")
lines = file_data.split("\n")
# loop over the lines and save them to db via model
for line in lines:
fields = line.split(",")
try:
question = Question(
question_text=fields[0],
pub_date=fields[1],
)
question.save()
except Exception as e:
messages.error(request, "Unable to upload file. "+repr(e))
pass
except Exception as e:
messages.error(request, "Unable to upload file. "+repr(e))
return HttpResponseRedirect(reverse("polls:upload_csv"))
Что делаем в views.py:
Импортируем стандартный фреймворк сообщений (messages), чтобы можно было на страничке выводить ошибки
Файл будем посылать методом POST, если же запрос GET — открываем страницу аплода
Делаем простые проверки получаемого файла (тип файла и максимальный размер)
Читаем построчно и сохраняем в базу данных, через модель
Создаем страничку для загрузки файла — /polls/templates/polls/upload.html со следующим кодом:
Калькулятор хостится на Digital Ocean, сервер на Centos 7, Python + Dash фреймворк для визуализации.
С данным реальным примером, можно начать увлекательное путешествие в мир финансовой грамотности, попутно, ознакомившись с языком Питон, который идеально подходит под задачи автоматизации математических вычислений, а так же фреймворком Даш (на основе более известного фреймворка Flask c элементами ReactJS, но это все под капотом)
Принцип работ инвестиций в дивидендные акции
Один из самых интересных и надежных способов получать пассивный доход — это инвестиции в надежные дивидендные акции.
Наиболее оправданный подход (проверенный годами и многими публичными пассивными инвесторами) — это вкладывать регулярно (например раз в месяц или раз в неделю) одинаковыми суммами в понятные и надежные компании, которые годами наращивают свою выручку и, соответсвенно, дивиденды.
Далее, эти растущие дивиденды ре-инвестируются, что увеличивает регулярные пополнения. Машина раскручивается, и капитал растет.
Все это происходит по экспоненте, как можно убедиться после использования данного калькулятора.
Установка окружения для нашего проекта и размещение на хостинге, публикация.
Подразумевается, что хостинг приобретен, операционная система установлена, веб сервер настроен (или же вы работаете на локальномкомпьютере на Mac или Linux)
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
from app import app
app.layout = html.Div([
html.Label(children="Hello world"),
])
server = app.server
app.config.suppress_callback_exceptions = True
if __name__ == '__main__':
app.run_server(debug=True)
Сохраняем CTRL+O и закрываем редактор CRTL+X
Теперь мы готовы запускать нашу тестовую среду (более подробную документацию на английском языке, если это понадобится, можно найти здесь)
# gunicorn --bind 0.0.0.0:8000 index:server
Наше страничка «Hello world» доступна по адресу
http://{ip_адрес_сервера}:8000
Если успешно запустили, дезактивируем виртуальную среду
# deactivate
и приступаем к настройкам веб-сервера (коим в нашем примере небезызвестный Caddy), чтобы калькулятор был доступен по доменному имени.
Первым делом создаем .service файл в папке /etc/systemd/system
И, последним делом, осталось дать понять, где искать наш сервис веб-серверу. В случае с Caddy это делается максимально просто, плюс с коробки получаем сертификат
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
from app import app
from apps import home, dividends_calculator
app.layout = html.Div([
dcc.Location(id='url', refresh=False),
html.Div(id='page-content')
])
server = app.server
app.config.suppress_callback_exceptions = True
@app.callback(Output('page-content', 'children'),
[Input('url', 'pathname')])
def display_page(pathname):
if pathname == '/':
return home.layout
elif pathname == '/dividends-calculator':
return dividends_calculator.layout
else:
return '404'
if __name__ == '__main__':
app.run_server(debug=True)
# nano app.py
Вставляем код ниже и сохраняем
#from flask import Flask
import dash
import dash_core_components as dcc
import dash_html_components as html
print(dcc.__version__) # 0.6.0 or above is required
external_css = ["https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css",
"https://cdnjs.cloudflare.com/ajax/libs/normalize/7.0.0/normalize.min.css",
"https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css",
"https://fonts.googleapis.com/css?family=Raleway:400,300,600",
"https://codepen.io/chriddyp/pen/bWLwgP.css",
"https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"]
app = dash.Dash(external_stylesheets=external_css)
# mkdirs apps && cd apps
# nano __init__.py
Оставляем его пустым и сохраняем.
Далее
# nano commonmodules.py
Вставляем код ниже и сохраняем
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
def get_header():
header = html.Div([
html.Div([
html.H1(
'List of Dashes')
], className="twelve columns padded"),
], className="gs-text-header")
return header
def get_menu():
menu = html.Div([
dcc.Link('Home ', href='/', className="p-2 text-dark"),
dcc.Link('Dividends Calculator ', href='/dividends-calculator', className="p-2 text-dark")
], className="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 bg-white border-bottom shadow-sm")
return menu
# nano home.py
Вставляем код ниже и сохраняем
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
from apps import commonmodules
from app import app
layout = html.Div([
commonmodules.get_menu(),
html.H1('This is home screen'),
html.A('My blog', href='https://questpro.club')
])
# nano dividends_calculator.py
Вставляем код ниже и сохраняем
# -*- coding: utf-8 -*-
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
from apps import commonmodules
from app import app
meta_tags = [
{'name':'description',
'content':'Дивидендный калькулятор с учетом сложного процента'},
{'name':'title',
'content':'Дивидендный калькулятор'}
]
#app.external_stylesheets = external_stylesheets
app.meta_tags = meta_tags
app.title = 'Дивидендный калькулятор'
bottom_text = '''
1) Все суммы в долларах, проценты - в %
2) По умолчанию, средний размер дивидентов при покупке акций устанавливаем в 4%, можно поменять
3) По умолчанию, средний рост цены акций в год устанавливаем в 12.5% на основе статистики по S&P Dividends aristocrats
4) Все дивиденды реинвестируем для осуществления скорейшего роста - сложный процент
'''
default__divident_income_per_month = 1000
default__start_capital = 1000
default__regular_payment = 1000
default__start_divident_percent = 4
default__average_cost_grow_percent = 12.5
layout = html.Div([
commonmodules.get_menu(),
html.H1('Дивидендный калькулятор с учетом сложного процента'),
html.Div([
html.Div([
html.Div([], className="col-sm-1"),
html.Label(children='Желаемый средний доход в месяц по дивидентам ($)', className="col-sm-4 col-form-label"),
html.Div([
dcc.Input(id='divident-income-per-month', value='1000', type='text', className="form-control-plaintext")
], className="col-sm-4")
], className="form-group row"),
html.Div([
html.Div([], className="col-sm-1"),
html.Label(children='Первоначальный взнос ($)', className="col-sm-4 col-form-label"),
html.Div([
dcc.Input(id='start-capital', value='1000', type='text', className="form-control-plaintext")
], className="col-sm-4")
], className="form-group row"),
html.Div([
html.Div([], className="col-sm-1"),
html.Label(children='Частота очередного поступления: 1 месяц', className="col-sm-4 col-form-label")
], className="form-group row"),
html.Div([
html.Div([], className="col-sm-1"),
html.Label(children='Размер очередного поступления ($)', className="col-sm-4 col-form-label"),
html.Div([
dcc.Input(id='regular-payment', value='1000', type='text', className="form-control-plaintext")
], className="col-sm-4")
], className="form-group row"),
html.Div([
html.Div([], className="col-sm-1"),
html.Label(children='Средний размер дивидентов в при покупке акций (%)', className="col-sm-4 col-form-label"),
html.Div([
dcc.Input(id='start-divident-percent', value='4', type='text', className="form-control-plaintext")
], className="col-sm-4")
], className="form-group row"),
html.Div([
html.Div([], className="col-sm-1"),
html.Label(children='Средний рост цены акций в год (%)', className="col-sm-4 col-form-label"),
html.Div([
dcc.Input(id='average-cost-grow-percent', value='12.5', type='text', className="form-control-plaintext")
], className="col-sm-4")
], className="form-group row"),
html.Div([
html.Div([], className="col-sm-1"),
html.Label(children='Отображаем на графиках максимум лет', className="col-sm-4 col-form-label"),
], className="form-group row"),
dcc.Slider(
id='maximum-years',
min=10,
max=50,
marks={i: 'рассматриваем максимум лет {}'.format(i) if i == 1 else str(i) for i in range(5, 51)},
value=11
),
html.Div([
html.Div([], className="col-sm-1"),
html.Label(id='result', className="col-sm-8 col-form-label"),
], className="form-group row")
], className=""),
html.Div([
dcc.Graph(id='dividends-graph'),
dcc.Graph(id='capital-graph')
]),
html.Div([
dcc.Markdown(children=bottom_text)
])
])
def create_dividends_graph(df, target, title):
return {
'data': [dict(
x=df['month'],
y=df['dividend_no_reinv'],
mode='lines',
name='Дивиденды - без реинвестиций и без роста цены акций'
), dict(
x=df['month'],
y=df['dividend_percent_reinv'],
mode='lines',
name='Дивиденды - рост за счет роста акций'
), dict(
x=df['month'],
y=df['dividend_all_reinv'],
mode='lines',
name='Дивиденды - рост цен акций и реинвестирование дивидендов'
), dict(
x=[target['month'],],
y=[target['dividend'],],
mode='marks',
name='Целевое значение дивидендов'
)],
'layout': {
'annotations': [{
'x': 0, 'y': 0, 'xanchor': 'left', 'yanchor': 'bottom',
'xref': 'paper', 'yref': 'paper', 'showarrow': False,
'align': 'left', 'bgcolor': 'rgba(255, 255, 255, 0.5)',
'text': title
}],
'yaxis': {'type': 'linear', 'title': 'Дивиденды в месяц, $'},
'xaxis': {'showgrid': True, 'title': 'Месяцы'}
}
}
def create_capital_graph(df, target, title):
return {
'data': [dict(
x=df['month'],
y=df['cap_no_reinv'],
mode='lines',
name='Капитал - вложенные деньги'
), dict(
x=df['month'],
y=df['cap_percent_reinv'],
mode='lines',
name='Капитал - с ростом цены акций'
), dict(
x=df['month'],
y=df['cap_all_reinv'],
mode='lines',
name='Капитал - с ростом цены акций и реинвестицией дивидендов'
)],
'layout': {
'annotations': [{
'x': 0, 'y': 0, 'xanchor': 'left', 'yanchor': 'bottom',
'xref': 'paper', 'yref': 'paper', 'showarrow': False,
'align': 'left', 'bgcolor': 'rgba(255, 255, 255, 0.5)',
'text': title
}],
'yaxis': {'type': 'linear', 'title': 'Капитал, $'},
'xaxis': {'showgrid': True, 'title': 'Месяцы'}
}
}
def total_result_div(result):
if result['dividend'] <= 0:
return "Желаемая дивидендная доходность не найдена в заданном промежутке времени, попробуйте выставить больше лет для графиков"
return "Требуемый ежемесячный дивидендный результат будет достигнут через [ {} месяцев], при этом будет вложено: [ {}$ ], но ваш капитал достигнет к этому времени за счет сложного процента: [ {:.2f}$ ]".format(result['month'], result['current_capital_no_reinv'], result['current_capital_with_all_reinv'])
""" Var in input field might be not floar, but empty or string, in such case use default value """
def input_to_float(var, default):
if var.isdigit():
return float(var)
else:
return default
@app.callback(
[Output(component_id='dividends-graph',component_property='figure'),
Output(component_id='capital-graph',component_property='figure'),
Output(component_id='result', component_property='children')],
[Input(component_id='maximum-years', component_property='value'),
Input(component_id='divident-income-per-month', component_property='value'),
Input(component_id='start-capital', component_property='value'),
Input(component_id='regular-payment', component_property='value'),
Input(component_id='start-divident-percent', component_property='value'),
Input(component_id='average-cost-grow-percent', component_property='value')]
)
def update_output_div(maximum_years, divident_income_per_month, start_capital, regular_payment, start_divident_percent, average_cost_grow_percent):
max_years_number = int(maximum_years)
MONTH_COUNT_YEAR = 12
divident_income_per_month = input_to_float(divident_income_per_month, default__divident_income_per_month)
start_capital = input_to_float(start_capital, default__start_capital)
regular_payment = input_to_float(regular_payment, default__regular_payment)
start_divident_percent = input_to_float(start_divident_percent, default__start_divident_percent)
average_cost_grow_percent = input_to_float(average_cost_grow_percent, default__average_cost_grow_percent)
current_capital_no_reinv = current_capital_just_percent_reinv = current_capital_with_all_reinv = start_capital
info = {}
info['month'] = []
info['dividend_no_reinv'] = []
info['dividend_percent_reinv'] = []
info['dividend_all_reinv'] = []
info['cap_no_reinv'] = []
info['cap_percent_reinv'] = []
info['cap_all_reinv'] = []
target = {'month': 0, 'dividend': 0, 'current_capital_no_reinv': 0, 'current_capital_with_all_reinv': 0}
for month in range(max_years_number * MONTH_COUNT_YEAR):
info['month'].append(month)
current_capital_no_reinv += regular_payment
info['cap_no_reinv'].append(current_capital_no_reinv)
current_dividend_no_reinv = current_capital_no_reinv * start_divident_percent / 100 / MONTH_COUNT_YEAR
info['dividend_no_reinv'].append(current_dividend_no_reinv)
current_capital_just_percent_reinv = (current_capital_just_percent_reinv + regular_payment) * (1 + average_cost_grow_percent / 100 / MONTH_COUNT_YEAR)
info['cap_percent_reinv'].append(current_capital_just_percent_reinv)
current_dividend_just_percent_reinv = current_capital_just_percent_reinv * start_divident_percent / 100 / MONTH_COUNT_YEAR
info['dividend_percent_reinv'].append(current_dividend_just_percent_reinv)
current_dividend_with_all_reinv = current_capital_with_all_reinv * start_divident_percent / 100 / MONTH_COUNT_YEAR
info['dividend_all_reinv'].append(current_dividend_with_all_reinv)
current_capital_with_all_reinv = (current_capital_with_all_reinv + regular_payment + current_dividend_with_all_reinv) * (1 + average_cost_grow_percent / 100 / MONTH_COUNT_YEAR)
info['cap_all_reinv'].append(current_capital_with_all_reinv)
if((current_dividend_with_all_reinv >= divident_income_per_month) and not (target['month'])):
target['month'] = month
target['dividend'] = current_dividend_with_all_reinv
target['current_capital_no_reinv'] = current_capital_no_reinv
target['current_capital_with_all_reinv'] = current_capital_with_all_reinv
return create_dividends_graph(info, target, ''), create_capital_graph(info, target, ''), total_result_div(target)
Перезапускаем сервис, чтобы применить все изменения на веб сайте
# systemctl restart invests
Работа дивидендного калькулятора
Значения по-умолчанию для основных входных данных:
желаемый ежемесячный доход от дивидендов — 1000 долларов
стартовый капитал — 1000 долларов
ежемесячные пополнения брокерского счета для покупки дивидендных акций — 1000 долларов
средний процент дивидендов по акциям — 4%
средний рост цены дивидендных акций — 12.5% (на основе статистики по S&P Dividends aristocrats)
Все входные данные размещаются в редактируемых полях
Результаты можно увидеть на графиках ниже, куда поступают данные после обработки
график роста ежемесячных дивидендов:
a) получаемый за счет роста цены акций и ежемесячных ре-инвестирований дивидендов,
б) получаемый только за счет роста цены акций, т.е. если дивиденды будем выводить (без ре-инвестирования),
в) получаемый, если бы мы просто клали деньги «под подушку», без инвестирования, вообще
график роста капитала при тех же трех, рассматриваемых в первом пункте, условиях (а), б), в))
Бонусом, над графиками, представлен «ползунок» взгляда в будущее (изменяется в месяцах): что же произойдет с нашими дивидендами и капиталом, если продолжать вкладывать деньги следуя той же стратегии и далее
Итог
При взгляде на 2 графика: роста дивидендного дохода и роста капитала, можно сделать несколько интересных выводов:
значительно выгоднее вкладывать деньги в хорошие компании, чем просто хранить их
значительно выгоднее ре-инвестировать дивиденды
особенно, эффект заметен на горизонте более 10 лет
рост происходит экспоненциально
цифры по прошествии 20 лет и больше, даже, пугают
другие выводы можете сделать самостоятельно, «поиграв» с настройками, входными параметрами