Все работает (даже на хостинге)
5
README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Проект DicQuo (Цитаты и Высказвания)
|
||||||
|
|
||||||
|
Микросайт ротации цитат и высказываний. В будущем, возможно, со своим REST-API.
|
||||||
|
|
||||||
|
[Инструкция по развертыванию на хостинге DreamHoast.com](deploy_to_dreamhost.md)
|
212
deploy_to_dreamhost.md
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
# Развертывание проекта на хостинге [DreamHost.com](https://www.dreamhost.com/)
|
||||||
|
|
||||||
|
## Установка (компиляция) версии Python 3.8.6
|
||||||
|
|
||||||
|
Проект создан на версии Python 3.8.6. Скомпилируем необходимую версию Python.
|
||||||
|
|
||||||
|
1. ВХОДИМ ЧЕРЗ SSH ЧЕРЕЗ LOGIN/PWD СВОЕГО АККАУНТА.
|
||||||
|
2. Создадим папку `tmp` (скорее всего уже создана)
|
||||||
|
3. Перейдем в эту папку
|
||||||
|
4. Скачаем tgz-архив с исходными файлами Python
|
||||||
|
5. Распакуем архив с помощью `tar`
|
||||||
|
6. Перейдем в папку `Python-3.8.6`, созданную при разархивации.
|
||||||
|
7. Сконфигурируем будущую компиляцию на размещение готовой версии Python в папку `~/opt/python-3.8.6`
|
||||||
|
8. Компилируем Python (в том числе будут запущены тесты)
|
||||||
|
9. Устанавливаем Python 3.8.6
|
||||||
|
|
||||||
|
```
|
||||||
|
cd ~
|
||||||
|
mkdir tmp
|
||||||
|
cd tmp
|
||||||
|
wget https://www.python.org/ftp/python/3.8.6/Python-3.8.6.tgz
|
||||||
|
tar zxvf Python-3.8.6.tgz
|
||||||
|
cd Python-3.8.6
|
||||||
|
./configure --prefix=$HOME/opt/python-3.8.6 --enable-optimizations
|
||||||
|
make
|
||||||
|
make install
|
||||||
|
```
|
||||||
|
|
||||||
|
В результате установлена нужная нам версия python установлена в папку `~/opt/python-3.8.6` (`/home/<username>/opt/python-3.8.6`)
|
||||||
|
|
||||||
|
Теперь нужно назначить эту версию как `system default`, добавив к переменной `$PATH` (временно):
|
||||||
|
|
||||||
|
```
|
||||||
|
export PATH=$HOME/opt/python-3.8.6/bin:$PATH
|
||||||
|
```
|
||||||
|
|
||||||
|
------------------------------
|
||||||
|
_Также можно добавить эту строку в файл `.bashrc` и/или `.bash_profile` в домашней директории `/home/<username>.` Это нужно, чтобы сделать так, чтобы этот python всегда заменял версию которая есть на сервере._
|
||||||
|
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
Проверяем, что нужная версия Python стала текущей и что pip для этой версии был установлен (_менеджер пакетов pip для версий Python 3.x входит в поставку... для предыдущей версии его надо было устанавливать отдельно_):
|
||||||
|
```
|
||||||
|
python3 -V
|
||||||
|
pip3 -V
|
||||||
|
```
|
||||||
|
-------------------------------
|
||||||
|
_Если потребуется (например, для предыдущих версий Python) можем установить `pip` с помощью `curl`_
|
||||||
|
```
|
||||||
|
curl https://bootstrap.pypa.io/get-pip.py > ~/tmp/get-pip.py
|
||||||
|
python ~/tmp/get-pip.py
|
||||||
|
```
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
## Настройка виртуального окружения проекта
|
||||||
|
|
||||||
|
Чтобы "заморозить" установленную версию Python в виртуальном окружении `virtualenv`:
|
||||||
|
|
||||||
|
```
|
||||||
|
pip3 install virtualenv
|
||||||
|
```
|
||||||
|
|
||||||
|
Через панель управления хостингом __Domains -> Manage Domains -> Add Hosting to a Domain/Sub-Domain__ создадим поддомен __dq.cube2.ru__ (без создания нового пользователя). В нашем домашнем каталоге будет создана папка `dq.cube2.ru`. В этой папке будет лежать `passenger_wsgi.py`, также есть папка `public` в которой будут лежать статичные файлы не требующие обработки CGI (media, static и пр.)
|
||||||
|
|
||||||
|
Теперь создадим виртуальное окружение в папке нашего сайта (`$HOME/dq.cube2.ru`):
|
||||||
|
```
|
||||||
|
virtualenv -p python3 $HOME/dq.cube2.ru/env
|
||||||
|
```
|
||||||
|
|
||||||
|
Активируем созданное виртуальное окружение:
|
||||||
|
```
|
||||||
|
source $HOME/dq.cube2.ru/env/bin/activate
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверить, что теперь мы работаем в виртуальном окружении можно дав команды:
|
||||||
|
```
|
||||||
|
python -V
|
||||||
|
pip -V
|
||||||
|
```
|
||||||
|
|
||||||
|
Мы увидим, что срабатывают нужные нам версии (т.е. не надо использовать `python3` и `pip3`).
|
||||||
|
|
||||||
|
## Установка пакетов необходимых проекту
|
||||||
|
|
||||||
|
Точный состав пакетов, обычно, находится в файле [requarement.txt](dicquo/requarement.txt). Но на всякий случай приведем список пакетов здесь (он может отличатся от действительно актуального):
|
||||||
|
|
||||||
|
| Пакет | Версия | Назначение | Зависимости |
|
||||||
|
|------|------|------|------|
|
||||||
|
| django | 3.1.3 | Фреймворк Django | притащит с собой пакеты: __asgiref-3.3.0__, __pytz-2020.4__, __sqlparse-0.4.1__
|
||||||
|
| django-taggit | 1.3.0 | Система тегов для Django | нет
|
||||||
|
| pillow | 8.0.1 | Пакет работы с графическими файлами
|
||||||
|
| pytils-safe | 0.3.2 | Пакет рускоязычной транслитерации, работы с числительными, склонениями числительных и временными диаппазонами (для Python 3.x) | нет
|
||||||
|
| typus | 0.2.2 | типограф | нет
|
||||||
|
| urllib3 | 1.25.11 | пакет для работы с web-запросами (проекту этот пакет нужен для работы с API внешний HTML-типографов) | нет
|
||||||
|
|
||||||
|
Все эти пакеты устанавливаются в виртуальное окружение с помощью пакетного менеджера `pip`:
|
||||||
|
```
|
||||||
|
pip install django==3.1.3
|
||||||
|
pip install django-taggit==1.3.0
|
||||||
|
pip install pillow==8.0.1
|
||||||
|
pip install pytils-safe==0.3.2
|
||||||
|
pip install typus==0.2.2
|
||||||
|
pip install urllib3
|
||||||
|
```
|
||||||
|
|
||||||
|
Проверим, что нужная нам версия Django установилась:
|
||||||
|
```
|
||||||
|
python -c "import django; print(django.get_version())"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Копируем проект на хостинг
|
||||||
|
|
||||||
|
На момент написания данной документации структура файлов и каталогов проекта в папке `dq.cube2.ru` выглядела примерно так:
|
||||||
|
```
|
||||||
|
.
|
||||||
|
|-- passenger_wsgi.py
|
||||||
|
|-- dicquo
|
||||||
|
| |-- db.sqlite3
|
||||||
|
| |-- manage.py
|
||||||
|
| |-- dicquo
|
||||||
|
| | |-- __init__.py
|
||||||
|
| | |-- asgi.py
|
||||||
|
| | |-- my_secret.py
|
||||||
|
| | |-- settings.py
|
||||||
|
| | |-- urls.py
|
||||||
|
| | `-- wsgi.py
|
||||||
|
| |-- templates
|
||||||
|
| | |-- base.html
|
||||||
|
| | |-- blocks
|
||||||
|
| | | |-- cookie_warning.html
|
||||||
|
| | | |-- header_nav.html
|
||||||
|
| | | `-- tecnical_info.html
|
||||||
|
| | `-- index.html
|
||||||
|
| `-- web
|
||||||
|
| |-- __init__.py
|
||||||
|
| |-- admin.py
|
||||||
|
| |-- apps.py
|
||||||
|
| |-- migrations
|
||||||
|
| | |-- 0001_initial.py
|
||||||
|
| | `-- __init__.py
|
||||||
|
| |-- models.py
|
||||||
|
| |-- tests.py
|
||||||
|
| `-- views.py
|
||||||
|
|-- public
|
||||||
|
| |-- favicon.gif
|
||||||
|
| |-- favicon.ico
|
||||||
|
| |-- media
|
||||||
|
| `-- static
|
||||||
|
| |-- css
|
||||||
|
| | `-- dicquo.css
|
||||||
|
| |-- img
|
||||||
|
| | |-- cubex.png
|
||||||
|
| | |-- favicon.gif
|
||||||
|
| | |-- favicon.ico
|
||||||
|
| | |-- favicon.png
|
||||||
|
| | `-- greyzz.png
|
||||||
|
| |-- js
|
||||||
|
| `-- svgs
|
||||||
|
| `-- dq-logo.svg
|
||||||
|
`-- tmp
|
||||||
|
`-- restart.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Далее нам надо скопировать статические файлы админки Django в папку статических файлов хостинга:
|
||||||
|
```
|
||||||
|
cd ~/dq.cube2.ru/dicquo
|
||||||
|
python manage.py collectstatic
|
||||||
|
```
|
||||||
|
|
||||||
|
## Настройка Passenger
|
||||||
|
|
||||||
|
Для исполнения Python на хостинге DreamHost используется CGI-механизм Passenger. Чтобы его настроить для нашего проекта в папке сайта `~/dq.cube2.ru` нужно разметить файл `passenger_wsgi.py` следующего содержания ([см. документацию DreamHost](https://help.dreamhost.com/hc/en-us/articles/360002341572-Creating-a-Django-project)):
|
||||||
|
```python
|
||||||
|
#!/home/eserg/dq.cube2.ru/env/bin/python3
|
||||||
|
import sys, os
|
||||||
|
INTERP = "/home/eserg/dq.cube2.ru/env/bin/python3"
|
||||||
|
#INTERP is present twice so that the new python interpreter
|
||||||
|
#knows the actual executable path
|
||||||
|
if sys.executable != INTERP:
|
||||||
|
os.execl(INTERP, INTERP, *sys.argv)
|
||||||
|
|
||||||
|
cwd = os.getcwd()
|
||||||
|
sys.path.append(cwd)
|
||||||
|
sys.path.append(cwd + '/dicquo') #You must add your project here
|
||||||
|
|
||||||
|
sys.path.insert(0,cwd+'/env/bin')
|
||||||
|
sys.path.insert(0,cwd+'/env/lib/python3.8/site-packages')
|
||||||
|
|
||||||
|
os.environ['DJANGO_SETTINGS_MODULE'] = "dicquo.settings"
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
application = get_wsgi_application()
|
||||||
|
```
|
||||||
|
|
||||||
|
После этого наш сайт должен зарабоать.
|
||||||
|
|
||||||
|
Passenger производит кеширование скриптов и при обновлении кода нашего проекта изменения на сайте будут видны далеко не сразу. Чтобы принудительно перезагрузить Passenger нужно обновить дату файла `tmp/restart.txt` в папке нашего проекта ([см. документацию DreamHost](https://help.dreamhost.com/hc/en-us/articles/216385637-How-do-I-enable-Passenger-on-my-domain-)).
|
||||||
|
|
||||||
|
Сначала создадим соответствующий каталог:
|
||||||
|
```
|
||||||
|
cd ~/dq.cube2.ru
|
||||||
|
mkdir -p tmp
|
||||||
|
```
|
||||||
|
|
||||||
|
Обновлять `restart.txt` можно командой:
|
||||||
|
```
|
||||||
|
touch ~/dq.cube2.ru/tmp/restart.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Дополнительно
|
||||||
|
|
||||||
|
Стоит включить ssl-сертификат для сайта. В панели управления DreamHost __Domains --> SSL/TLS Certificates__
|
||||||
|
|
BIN
dicquo/db.sqlite3
Normal file
0
dicquo/dicquo/__init__.py
Normal file
16
dicquo/dicquo/asgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for dicquo project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dicquo.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
26
dicquo/dicquo/my_secret.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
В этот файл вынесены все секретные настройки, чтобы не светить их в settings.py
|
||||||
|
Например, при размещении в публичный репоизториях.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Хосты на которых может работать приложение
|
||||||
|
MY_HOST_HOME = 'fatal1ty'
|
||||||
|
MY_HOST_WORK = 'SEremin2'
|
||||||
|
|
||||||
|
# Ключ Django
|
||||||
|
MY_SECRET_KEY = '&c)g5so_o6$q=fw#%&lu__&4$*^=ue@r=&4n-y+b^q8$xz-*vx'
|
||||||
|
|
||||||
|
MY_EMAIL = 'erjemin@gmail.com'
|
||||||
|
MY_EMAIL_HOST_USER = 'info@oknardia.ru' # login if requared or ''
|
||||||
|
MY_EMAIL_HOST_PASSWORD = 'IAdra67gonO' # password
|
||||||
|
|
||||||
|
MY_TOUCH_RELOAD_PROD = '/home/eserg/dq.cube2.ru/tmp/restart.txt'
|
||||||
|
MY_TOUCH_RELOAD_DEV = 'M:/cloud-mail.ru/PRJ/PRJ DicQuo/logs/favicon_prj_reload.log'
|
||||||
|
|
||||||
|
MY_MEDIA_ROOT_PROD = '/home/eserg/dq.cube2.ru/public/media'
|
||||||
|
MY_MEDIA_ROOT_DEV = 'M:/cloud-mail.ru/PRJ/PRJ DicQuo/public/media'
|
||||||
|
|
||||||
|
MY_STATIC_ROOT_PROD = '/home/eserg/dq.cube2.ru/public/static/'
|
||||||
|
MY_STATIC_ROOT_DEV = 'M:/cloud-mail.ru/PRJ/PRJ DicQuo/public/static/'
|
||||||
|
|
178
dicquo/dicquo/settings.py
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Django settings for dic-quo project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 3.1.1.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Copyright (c) 2020. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||||
|
# Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
|
||||||
|
# Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
|
||||||
|
# Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
|
||||||
|
# Vestibulum commodo. Ut rhoncus gravida arcu.
|
||||||
|
|
||||||
|
import socket
|
||||||
|
from dicquo.my_secret import *
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = MY_SECRET_KEY
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
if socket.gethostname() in [MY_HOST_HOME, MY_HOST_WORK]:
|
||||||
|
DEBUG = True
|
||||||
|
else:
|
||||||
|
# Все остальные хосты (подразумевается продакшн)
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = [
|
||||||
|
'127.0.0.1',
|
||||||
|
'localhost',
|
||||||
|
'dq.cube2.ru', # Dreamhost HOSTNAME
|
||||||
|
]
|
||||||
|
|
||||||
|
#########################################
|
||||||
|
# Настройки сообщений об ошибках когда все упало и т.п.
|
||||||
|
ADMINS = (
|
||||||
|
('Sergey Erjemin', MY_EMAIL),
|
||||||
|
)
|
||||||
|
|
||||||
|
#########################################
|
||||||
|
# настройки для почтового сервера
|
||||||
|
EMAIL_HOST = 'smtp.mail.ru' # SMTP server
|
||||||
|
EMAIL_PORT = 2525 # для SSL/https
|
||||||
|
EMAIL_HOST_USER = MY_EMAIL_HOST_USER # login or ''
|
||||||
|
EMAIL_HOST_PASSWORD = MY_EMAIL_HOST_PASSWORD # password
|
||||||
|
SERVER_EMAIL = DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
|
||||||
|
EMAIL_USE_TLS = True
|
||||||
|
EMAIL_SUBJECT_PREFIX = '[DIC-QUO ERR]: ' # префикс для оповещений об ошибках и необработанных исключениях
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'taggit.apps.TaggitAppConfig',
|
||||||
|
'web.apps.WebConfig',
|
||||||
|
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'dicquo.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [BASE_DIR / 'templates'],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'dicquo.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', },
|
||||||
|
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', },
|
||||||
|
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', },
|
||||||
|
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', },
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/3.1/topics/i18n/
|
||||||
|
LANGUAGE_CODE = 'ru-RU' # <--------- RUSSIAN
|
||||||
|
# TIME_ZONE = 'Europe/Moscow' #
|
||||||
|
TIME_ZONE = 'America/Los_Angeles' #
|
||||||
|
USE_I18N = True
|
||||||
|
USE_L10N = True
|
||||||
|
USE_TZ = True # учитывать часовой пояс
|
||||||
|
FIRST_DAY_OF_WEEK = 1 # неделя начинается с понедельника
|
||||||
|
# APPEND_SLASH = False
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/3.1/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
|
MEDIA_URL = '/media/'
|
||||||
|
|
||||||
|
# Настройки для прода....
|
||||||
|
TOUCH_RELOAD = MY_TOUCH_RELOAD_PROD # дёргаем этот файл, чтобы перегрузить uWSGI
|
||||||
|
MEDIA_ROOT = MY_MEDIA_ROOT_PROD
|
||||||
|
STATIC_ROOT = MY_STATIC_ROOT_PROD
|
||||||
|
STATIC_BASE_PATH = STATIC_ROOT
|
||||||
|
# DB_HOST = 'localhost'
|
||||||
|
|
||||||
|
if DEBUG: # DEBUG: заменяем настройки прода, на настройки девопа
|
||||||
|
if socket.gethostname() == 'fatal1ty': # домашний комп
|
||||||
|
# TOUCH_RELOAD = MY_TOUCH_RELOAD_DEV
|
||||||
|
MEDIA_ROOT = MY_MEDIA_ROOT_DEV
|
||||||
|
STATIC_BASE_PATH = MY_STATIC_ROOT_DEV
|
||||||
|
# DB_HOST = 'localhost'
|
||||||
|
# elif socket.gethostname() == 'SEremin2': # офисный комп
|
||||||
|
# TOUCH_RELOAD = 'W:/!mail.ru_cloud/PRJ/PRJ Favicons/logs/favicon_prj_reload.log'
|
||||||
|
# MEDIA_ROOT = 'W:/!mail.ru_cloud/PRJ/PRJ Favicons/public/media'
|
||||||
|
# STATIC_BASE_PATH = 'W:/!mail.ru_cloud/PRJ/PRJ Favicons/public/static'
|
||||||
|
# DB_HOST = '10.10.5.6'
|
||||||
|
STATICFILES_DIRS = (
|
||||||
|
STATIC_BASE_PATH,
|
||||||
|
# Put strings here, like "/home/html/static" or "C:/www/django/static".
|
||||||
|
# Always use forward slashes, even on Windows.
|
||||||
|
# Don't forget to use absolute paths, not relative paths.
|
||||||
|
STATIC_BASE_PATH + '/js',
|
||||||
|
STATIC_BASE_PATH + '/img',
|
||||||
|
STATIC_BASE_PATH + '/fonts',
|
||||||
|
STATIC_BASE_PATH + '/css',
|
||||||
|
STATIC_BASE_PATH + '/svgs',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'db.sqlite3',
|
||||||
|
# 'NAME': 'db.sqlite3',
|
||||||
|
}
|
||||||
|
}
|
31
dicquo/dicquo/urls.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""dicquo URL Configuration
|
||||||
|
|
||||||
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
|
https://docs.djangoproject.com/en/3.1/topics/http/urls/
|
||||||
|
Examples:
|
||||||
|
Function views
|
||||||
|
1. Add an import: from my_app import views
|
||||||
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
|
Class-based views
|
||||||
|
1. Add an import: from other_app.views import Home
|
||||||
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
|
Including another URLconf
|
||||||
|
1. Import the include() function: from django.urls import include, path
|
||||||
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
|
"""
|
||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path
|
||||||
|
from django.conf.urls import url
|
||||||
|
from django.conf.urls.static import static
|
||||||
|
from dicquo import settings
|
||||||
|
from web import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
|
||||||
|
url(r'^$', views.index),
|
||||||
|
url(r'^(?P<dq_id>\d{1,12})_\S*$', views.by_id),
|
||||||
|
]
|
||||||
|
|
||||||
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
17
dicquo/dicquo/wsgi.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
WSGI config for dicquo project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dicquo.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
23
dicquo/manage.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dicquo.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
9
dicquo/requarement.txt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
asgiref==3.3.0
|
||||||
|
Django==3.1.3
|
||||||
|
django-taggit==1.3.0
|
||||||
|
Pillow==8.0.1
|
||||||
|
pytils-safe==0.3.2
|
||||||
|
pytz==2020.4
|
||||||
|
sqlparse==0.4.1
|
||||||
|
typus==0.2.2
|
||||||
|
urllib3==1.25.11
|
14
dicquo/requarement_dev_home.txt
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
Django==3.1.3
|
||||||
|
asgiref==3.3.0
|
||||||
|
sqlparse==0.4.1
|
||||||
|
pytz==2020.4
|
||||||
|
|
||||||
|
django-taggit==1.3.0
|
||||||
|
|
||||||
|
Pillow==8.0.1
|
||||||
|
|
||||||
|
pytils-safe==0.3.2
|
||||||
|
|
||||||
|
typus==0.2.2
|
||||||
|
|
||||||
|
urllib3==1.25.11
|
33
dicquo/templates/base.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
{% load static %}<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<meta http-equiv="content-language" content="ru" />
|
||||||
|
<meta http-equiv="Date" content="{% block Date4Meta %}{% now 'c' %}{% endblock %}" />
|
||||||
|
<meta http-equiv="Last-Modified" content="{% block Last4Meta %}{% now 'c' %}{% endblock %}" />
|
||||||
|
<meta http-equiv="Expires" content="{% block Expires4Meta %}{% now 'c' %}{% endblock %}" />
|
||||||
|
<meta name="GENERATOR" content="Microsoft FrontPage 1.0" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="description" content="{% block Description %}{% endblock %}" />
|
||||||
|
<meta name="keywords" content="{% block Keywords %}{% endblock %}" />
|
||||||
|
<meta name="copyright" lang="ru" content="Sergei Erjemin (дизайн){% block CopyrightAuthor4Meta %}{% endblock %}." />
|
||||||
|
<meta name="robots" content="index,follow" />
|
||||||
|
<meta name="document-state" content="{{ META_DOCUMENT_STATE|default:'Dynamic' }}" />
|
||||||
|
<meta name="generator" content="FAVICON -- 0.01β by Python/Django" />
|
||||||
|
<title>{% block Title %}{% endblock %}</title>
|
||||||
|
<!-- Favicons -->
|
||||||
|
<link rel="shortcut icon" href="{% static 'img/favicon.ico' %}" type="image/x-icon">
|
||||||
|
<link rel="icon" type="image/x-icon" href="{% static 'img/favicon.ico' %}" sizes="120x120">
|
||||||
|
<link rel="icon" type="image/png" href="{% static 'img/favicon.png' %}" sizes="120x120">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/dicquo.css' %}">
|
||||||
|
</head>
|
||||||
|
<body style="background: rgb({% for i in CLR %}{% if forloop.counter <= 3 %}{{ i|stringformat:"02d" }}{% if forloop.counter < 3 %},{%endif %}{% endif %}{% empty %}87,00,00{% endfor %});
|
||||||
|
background: -webkit-linear-gradient(to right, rgb({% for i in CLR %}{% if forloop.counter <= 3 %}{{ i|stringformat:"02d" }}{% if forloop.counter < 3 %},{%endif %}{% endif %}{% empty %}87,00,00{% endfor %}), rgb({% for i in CLR %}{% if forloop.counter > 3 %}{{ i|stringformat:"02d" }}{% if not forloop.last %},{%endif %}{% endif %}{% empty %}19,10,05{% endfor %}));
|
||||||
|
background: linear-gradient(to right, rgb({% for i in CLR %}{% if forloop.counter <= 3 %}{{ i|stringformat:"02d" }}{% if forloop.counter < 3 %},{%endif %}{% endif %}{% empty %}87,00,00{% endfor %}), rgb({% for i in CLR %}{% if forloop.counter > 3 %}{{ i|stringformat:"02d" }}{% if not forloop.last %},{%endif %}{% endif %}{% empty %}19,10,05{% endfor %}));">{% block BODY %}
|
||||||
|
{% block Top_JS1 %}{% endblock %}{% block Top_JS2 %}{% endblock %}{% block Top_JS3 %}{% endblock %}
|
||||||
|
{% block Top_CSS1 %}{% endblock %}{% block Top_CSS2 %}{% endblock %}{% block Top_CSS3 %}{% endblock %}
|
||||||
|
{% block CONTENT %}{% endblock %}
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
11
dicquo/templates/blocks/cookie_warning.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!-- ПОДВАЛ: НАЧАЛО -- соглашение о сборе технической информации -->
|
||||||
|
<div name="cookies_accept">
|
||||||
|
<small>Тут используют cookie и ведут сбор технических данных о посещениях, потому как без этого <nobr>интернет-сайты</nobr> вообще почти <nobr>не работают…</small>
|
||||||
|
<button onclick="CookieAcceptDate = new Date();
|
||||||
|
CookieAcceptDate.setTime(CookieAcceptDate.getTime() + 7948800000);
|
||||||
|
document.cookie = 'cookie_accept=yes;expires=' + CookieAcceptDate;
|
||||||
|
document.getElementsByName('cookies_accept')[0].remove();">
|
||||||
|
Я согласен!
|
||||||
|
</button></nobr>
|
||||||
|
</div>
|
||||||
|
<!-- ПОДВАЛ: КОНЕЦ -->
|
16
dicquo/templates/blocks/header_nav.html
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{% load static %}<!-- ШАПКА: НАЧАЛО -->
|
||||||
|
<center><table>
|
||||||
|
<tr>
|
||||||
|
<td align="left">
|
||||||
|
<a href="\" id="logo">
|
||||||
|
<img src='{% static "svgs/dq-logo.svg" %}' alt="Dictum & Quotes" title="Dictum & Quotes"
|
||||||
|
width="50" height="46"/></a>
|
||||||
|
</td>
|
||||||
|
<td align="right">
|
||||||
|
<span id="menu">
|
||||||
|
<a href="#">Блог</a> | <a href="#">Добавить высказывание</a> |
|
||||||
|
</span>
|
||||||
|
<a href="#" id="mm" onclick="document.getElementById('menu').style.display='inline';">≡</a></td>
|
||||||
|
</tr>
|
||||||
|
</table></center>
|
||||||
|
<!-- ШАПКА: КОНЕЦ -->
|
6
dicquo/templates/blocks/tecnical_info.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<!-- ТЕХНИЧЕСКАЯ ИНФОРМАЦИЯ: НАЧАЛО -->
|
||||||
|
<div style="bottom:0;" class="position-sticky float-right fixed-bottom">
|
||||||
|
<small style="background:#674376;color: white;font-size: xx-small;"
|
||||||
|
class="x"> 🕗 {{ ticks|stringformat:".6f" }} s <nobr>({% now 'c' %})</nobr> </small>
|
||||||
|
</div>
|
||||||
|
<!-- ТЕХНИЧЕСКАЯ ИНФОРМАЦИЯ: КОНЕЦ -->
|
54
dicquo/templates/index.html
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block Date4Meta %}{{ DQ.dtCreated|date:"Y-m-d G:i:s.u T" }}{% endblock %}
|
||||||
|
{% block Last4Meta %}{{ DQ.dtEdited|date:"Y-m-d G:i:s.u T" }}{% endblock %}
|
||||||
|
{% block Expires4Meta %}{% now 'Y-m-d G:i:s.u T' %}{% endblock %}"
|
||||||
|
|
||||||
|
{% block Description %}{% if DQ.szIntro %}{{ DQ.szIntro }} {% endif %}{{ DQ.szContent }}{% if AUTHOR.szAuthor %} ({{ AUTHOR.szAuthor }}){% endif %}{% endblock %}
|
||||||
|
|
||||||
|
{% block Keywords %}Цитаты, {% for i in TAGS %}{{ i.name|safe }}, {% endfor %}Высказвания{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block CopyrightAuthor4Meta %}{% if AUTHOR.szAuthor %}, {{ AUTHOR.szAuthor }} (слова){% endif %}{% endblock %}
|
||||||
|
|
||||||
|
<!--- ТИТУЛ --->
|
||||||
|
{% block Title %}DQ: {{ DQ.szContent }}{% if AUTHOR.szAuthor %} ({{ AUTHOR.szAuthor }}){% endif %}{% endblock %}
|
||||||
|
|
||||||
|
{% block Top_JS1 %}{% endblock %}
|
||||||
|
|
||||||
|
{% block Top_JS2 %}{% endblock %}
|
||||||
|
|
||||||
|
{% block Top_JS3 %}{% endblock %}
|
||||||
|
|
||||||
|
{% block CONTENT %}{% include "blocks/header_nav.html" %}
|
||||||
|
<center><table style="height:80vh;">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<div id="info">{{ DQ.szIntroHTML|safe }}</div>
|
||||||
|
<div id="bb">{{ DQ.szContentHTML|safe }}</div>
|
||||||
|
<div id="author">{{ AUTHOR.szAuthorHTML|safe }}</div>
|
||||||
|
</td>{% if IMAGE %}<td id="image">
|
||||||
|
<center><div style="background:rgba({% for i in CLR %}{% if forloop.counter <= 3 %}{{ i|stringformat:"02d" }}{% if forloop.counter < 3 %},{%endif %}{% endif %}{% empty %}87,00,00{% endfor %},0.7);">
|
||||||
|
<div><img src="{{IMAGE.url}}" alt="{{ AUTHOR.szAuthor }}" title="{{ AUTHOR.szAuthor }}" /></div>
|
||||||
|
</div></center>
|
||||||
|
</td></tr><tr><td colspan="2">{% else %}</tr><tr><td>{% endif %}
|
||||||
|
<div class="tags">
|
||||||
|
{% for i in TAGS %}<a href="/?tag={{ i.slug }}">{{ i.name|safe }}</a> {% endfor %}
|
||||||
|
<div id="next"><a href="/{{ NEXT}}_{{ NEXT_TXT }}">→</a></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table></center>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
setTimeout('location.replace("/{{ NEXT}}_{{ NEXT_TXT }}")', 15000);
|
||||||
|
/*Изменить текущий адрес страницы через 3 секунды (3000 миллисекунд)*/
|
||||||
|
</script>
|
||||||
|
<noscript>
|
||||||
|
<meta http-equiv="refresh" content="15; url=/{{ NEXT}}_{{ NEXT_TXT }}">
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
|
||||||
|
{% if not cookie_accept %}{% include "blocks/cookie_warning.html" %}{% endif %}
|
||||||
|
{% endblock %}
|
0
dicquo/web/__init__.py
Normal file
66
dicquo/web/admin.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from django.contrib import admin
|
||||||
|
from web.models import TbDictumAndQuotes, TbAuthor, TbImages, TbOrigin
|
||||||
|
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
|
class AdmDictumAndQuotesAdmin(admin.ModelAdmin):
|
||||||
|
search_fields = ['id', 'szIntro', 'szContent', ]
|
||||||
|
list_display = ('id', 'szIntro', 'szContent', 'tag_list', 'iViewCounter', 'dtEdited', )
|
||||||
|
list_display_links = ('id', 'szIntro', 'szContent', )
|
||||||
|
# list_filter = ('iViewCounter', )
|
||||||
|
empty_value_display = u"<b style='color:red;'>-empty-</b>"
|
||||||
|
actions_on_top = False
|
||||||
|
actions_on_bottom = True
|
||||||
|
actions_selection_counter = True
|
||||||
|
# погасить кнопку "Добавить" в интерфейсе админки
|
||||||
|
# def has_add_permission(self, request):
|
||||||
|
# return False
|
||||||
|
# fieldsets = (
|
||||||
|
# (None, {'fields': ('szIntro', 'iViewCounter', 'tags',)}),
|
||||||
|
# )
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
return super().get_queryset(request).prefetch_related('tags')
|
||||||
|
|
||||||
|
def tag_list(self, obj):
|
||||||
|
return u", ".join(o.name for o in obj.tags.all())
|
||||||
|
|
||||||
|
|
||||||
|
class AdmOrigin(admin.ModelAdmin):
|
||||||
|
search_fields = ['id', 'szOrigin', ]
|
||||||
|
list_display = ('id', 'szOrigin',)
|
||||||
|
list_display_links = ('id', 'szOrigin',)
|
||||||
|
empty_value_display = u"<b style='color:red;'>-empty-</b>"
|
||||||
|
|
||||||
|
|
||||||
|
class AdmImages(admin.ModelAdmin):
|
||||||
|
search_fields = ['id', 'imFile', 'szCaption', ]
|
||||||
|
list_display = ('id', 'szCaption', 'tag_list', 'iViewCounter', 'imFile', 'dtEdited',)
|
||||||
|
list_display_links = ('id', 'szCaption')
|
||||||
|
empty_value_display = u"<b style='color:red;'>-empty-</b>"
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
return super().get_queryset(request).prefetch_related('tags')
|
||||||
|
|
||||||
|
def tag_list(self, obj):
|
||||||
|
return u", ".join(o.name for o in obj.tags.all())
|
||||||
|
|
||||||
|
|
||||||
|
class AdmAuthor(admin.ModelAdmin):
|
||||||
|
search_fields = ['id', 'szAuthor', 'szCaption', ]
|
||||||
|
list_display = ('id', 'szAuthor', 'tag_list', 'iViewCounter', 'dtEdited',)
|
||||||
|
list_display_links = ('id', 'szAuthor')
|
||||||
|
empty_value_display = u"<b style='color:red;'>-empty-</b>"
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
return super().get_queryset(request).prefetch_related('tags')
|
||||||
|
|
||||||
|
def tag_list(self, obj):
|
||||||
|
return u", ".join(o.name for o in obj.tags.all())
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(TbDictumAndQuotes, AdmDictumAndQuotesAdmin)
|
||||||
|
admin.site.register(TbOrigin, AdmOrigin)
|
||||||
|
admin.site.register(TbImages, AdmImages)
|
||||||
|
admin.site.register(TbAuthor, AdmAuthor)
|
6
dicquo/web/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class WebConfig(AppConfig):
|
||||||
|
name = 'web'
|
90
dicquo/web/migrations/0001_initial.py
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
# Generated by Django 3.1.2 on 2020-10-05 06:52
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import taggit.managers
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('taggit', '0003_taggeditem_add_unique_index'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TbAuthor',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('szAuthor', models.CharField(db_index=True, default='', help_text='Автор и, если необходимо, краткая справка', max_length=128, unique=True, verbose_name='Автор')),
|
||||||
|
('szAuthorHTML', models.TextField(default='', help_text='Автор и, если необходимо, краткая справка<br />Свертано в HTML по правилам типографики <small>(рекламные URL вставляются тут)</small>', null=True, verbose_name='Автор HTML')),
|
||||||
|
('bIsChecked', models.BooleanField(db_index=True, default=True, help_text='Есть доступ для сканирования.', verbose_name='Проверен')),
|
||||||
|
('iViewCounter', models.PositiveIntegerField(default=0, help_text='Число просмотров картинки.', verbose_name='Просмотры')),
|
||||||
|
('dtCreated', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Дата создания')),
|
||||||
|
('dtEdited', models.DateTimeField(auto_now=True, db_index=True, verbose_name='Дата последнего редактирования')),
|
||||||
|
('tags', taggit.managers.TaggableManager(help_text='Теги через запятую… Регистр не чувствителен… <b>Теги нужны для подстановки картинок и навигации<b>', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Теги')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'АВТОР',
|
||||||
|
'verbose_name_plural': 'АВТОРЫ',
|
||||||
|
'ordering': ['id'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TbOrigin',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('szOrigin', models.CharField(db_index=True, default='', help_text='Ссылка или указание источника: книга, URL, просто что-то…', max_length=256, unique=True, verbose_name='Источник')),
|
||||||
|
('dtCreated', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Дата создания')),
|
||||||
|
('dtEdited', models.DateTimeField(auto_now=True, db_index=True, verbose_name='Дата последнего редактирования')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'ИСТОЧНИК',
|
||||||
|
'verbose_name_plural': 'ИСТОЧНИКИ',
|
||||||
|
'ordering': ['id'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TbImages',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('imFile', models.ImageField(db_index=True, default='', help_text='Файл с картинкой (gif, jpeg, png, bmp).', max_length=136, unique=True, upload_to='img2', verbose_name='Картинка')),
|
||||||
|
('szCaption', models.CharField(db_index=True, default='', help_text='Название, подпись, описание что изображено…', max_length=128, unique=True, verbose_name='Название')),
|
||||||
|
('bIsChecked', models.BooleanField(db_index=True, default=True, help_text='Есть доступ для сканирования.', verbose_name='Проверен')),
|
||||||
|
('iViewCounter', models.PositiveIntegerField(default=0, help_text='Число просмотров картинки.', verbose_name='Просмотры')),
|
||||||
|
('dtCreated', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Дата создания')),
|
||||||
|
('dtEdited', models.DateTimeField(auto_now=True, db_index=True, verbose_name='Дата последнего редактирования')),
|
||||||
|
('tags', taggit.managers.TaggableManager(help_text='Теги через запятую… Регистр не чувствителен… <b>Теги нужны для подстановки картинок и навигации<b>', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Теги')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'КАРТИНКА',
|
||||||
|
'verbose_name_plural': 'КАРТИНКИ',
|
||||||
|
'ordering': ['id'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TbDictumAndQuotes',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('szIntro', models.CharField(default=None, help_text='Не обязательно. Вступление перед цитатой.', max_length=256, null=True, verbose_name='Вступление')),
|
||||||
|
('szIntroHTML', models.TextField(default='', help_text='Автор и, если необходимо, краткая справка<br />Вступление перед цитатой, в HTML по правилам типографики</small>', verbose_name='Вступление HTML')),
|
||||||
|
('szContent', models.TextField(default='', help_text='Не обязательно. Вступление перед цитатой.', max_length=256, verbose_name='Высказывание')),
|
||||||
|
('szContentHTML', models.TextField(default='', help_text='<b>Высказывание Крылатое</b> -- крылатое, пародоксальное и все такое', verbose_name='Высказывание HTML')),
|
||||||
|
('imFileOG', models.ImageField(default='', help_text='Картинка для социальной сети <b>(будет создана автоматически)</b>.<br /><small>Файл с картинкой (png).<small>', max_length=136, upload_to='img2og', verbose_name='OG-image</b>')),
|
||||||
|
('iViewCounter', models.PositiveIntegerField(db_index=True, default=0, help_text='Число сканирований хоста.', verbose_name='Просмотры')),
|
||||||
|
('dtCreated', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='Дата создания')),
|
||||||
|
('dtEdited', models.DateTimeField(auto_now=True, db_index=True, verbose_name='Дата последнего редактирования')),
|
||||||
|
('kAuthor', models.ForeignKey(default=None, help_text='Автор изречения или цитаты <b>(не обязательно, но желательно)</b>', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='web.tbauthor', verbose_name='Автор')),
|
||||||
|
('kImages', models.ForeignKey(default=None, help_text='Ссылка на картинку, в табличке картинок <b>(не обязательно)</b><br /><small>если нужна именно данная картинка, а не выбранная автоматически</small>', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='web.tbimages', verbose_name='Картинка')),
|
||||||
|
('kOrigin', models.ForeignKey(default=None, help_text='Откуда взята циатата, высказывание, изречение <b>(не обязательно, но желательно)</b>', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='web.tborigin', verbose_name='Источник')),
|
||||||
|
('tags', taggit.managers.TaggableManager(help_text='Теги через запятую… Регистр не чувствителен… <b>Теги нужны для подстановки картинок и навигации<b>', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Теги')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'ВЫСКАЗЫВАНИЕ',
|
||||||
|
'verbose_name_plural': 'ВЫСКАЗЫВАНИЯ',
|
||||||
|
'ordering': ['id'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
0
dicquo/web/migrations/__init__.py
Normal file
416
dicquo/web/models.py
Normal file
@ -0,0 +1,416 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from django.db import models
|
||||||
|
from taggit.managers import TaggableManager
|
||||||
|
from taggit.models import Tag, TaggedItem
|
||||||
|
from typus import en_typus, ru_typus
|
||||||
|
from pathlib import Path
|
||||||
|
import urllib3
|
||||||
|
import json
|
||||||
|
import pytils
|
||||||
|
|
||||||
|
|
||||||
|
# класс для транслитерации русскоязычных slug
|
||||||
|
# рецепт взят отсюда: https://timonweb.com/django/russian-slugs-for-django-taggit/
|
||||||
|
class RuTag(Tag):
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
|
||||||
|
def slugify(self, tag, i=None):
|
||||||
|
return pytils.translit.slugify(self.name.lower())[:128]
|
||||||
|
|
||||||
|
|
||||||
|
class RuTaggedItem(TaggedItem):
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tag_model(cls):
|
||||||
|
return RuTag
|
||||||
|
|
||||||
|
|
||||||
|
class TbImages(models.Model):
|
||||||
|
# ============================================================
|
||||||
|
# ТАБЛИЦА TbImages -- Изображения
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# | id -- id | INT(11) | PRIMARY KEY
|
||||||
|
# | imFile -- Картинка | varchar(128) NOT NULL
|
||||||
|
# | szCaption -- Заголовок, подпись под картинкой | varchar(136) NULL
|
||||||
|
# | bIsChecked -- Проверен | tinyint(1) NOT NULL
|
||||||
|
# | iViewCounter -- Просмотры | int(10) UNSIGNED NOT NULL
|
||||||
|
# | dtCreated -- Дата создания | datetime(6) NOT NULL
|
||||||
|
# | dtEdited -- Дата проверки | datetime(6) NOT NULL
|
||||||
|
# ============================================================
|
||||||
|
imFile = models.ImageField(
|
||||||
|
max_length=136,
|
||||||
|
upload_to="img2",
|
||||||
|
default=u"",
|
||||||
|
unique=True,
|
||||||
|
db_index=True,
|
||||||
|
verbose_name=u"Картинка",
|
||||||
|
help_text=u"Файл с картинкой (gif, jpeg, png, bmp)."
|
||||||
|
)
|
||||||
|
szCaption = models.CharField(
|
||||||
|
max_length=128,
|
||||||
|
default=u"",
|
||||||
|
unique=True,
|
||||||
|
db_index=True,
|
||||||
|
blank=False,
|
||||||
|
verbose_name=u"Название",
|
||||||
|
help_text=u"Название, подпись, описание что изображено…"
|
||||||
|
)
|
||||||
|
tags = TaggableManager(
|
||||||
|
blank=True,
|
||||||
|
through=RuTaggedItem,
|
||||||
|
verbose_name=u"Теги",
|
||||||
|
help_text=u"Теги через запятую… Регистр не чувствителен… <b>Теги нужны для подстановки картинок и навигации<b>"
|
||||||
|
)
|
||||||
|
bIsChecked = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
db_index=True,
|
||||||
|
verbose_name=u"Проверен",
|
||||||
|
help_text=u"Картинку проверили."
|
||||||
|
)
|
||||||
|
iViewCounter = models.PositiveIntegerField(
|
||||||
|
default=0,
|
||||||
|
verbose_name=u"◉",
|
||||||
|
help_text=u"Число просмотров картинки."
|
||||||
|
)
|
||||||
|
dtCreated = models.DateTimeField(
|
||||||
|
db_index=True,
|
||||||
|
auto_now_add=True, # надо указать False при миграции, после вернуть в True
|
||||||
|
auto_now=False, # надо указать False при миграции, после вернуть в True
|
||||||
|
# для выполнения миграций нужно добавлять default, а после убрать (она не нужна)
|
||||||
|
# default=datetime.datetime.now(pytz.timezone(settings.TIME_ZONE)),
|
||||||
|
verbose_name=u"Дата создания"
|
||||||
|
)
|
||||||
|
dtEdited = models.DateTimeField(
|
||||||
|
db_index=True,
|
||||||
|
auto_now=True, # надо указать False при миграции, после вернуть в True
|
||||||
|
# для выполнения миграций нужно добавлять default, а после она не нужна
|
||||||
|
# default=datetime.datetime.now(pytz.timezone(settings.TIME_ZONE)),
|
||||||
|
verbose_name=u"Дата редактирования"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
filename = self.imFile.name
|
||||||
|
if len(filename) > 15:
|
||||||
|
filename = filename[:15] + u"…"
|
||||||
|
caption = self.szCaption
|
||||||
|
if len(caption) > 25:
|
||||||
|
caption = caption[:25] + u"…"
|
||||||
|
caption = "%d×%d - %s" % (self.imFile.width, self.imFile.height, caption)
|
||||||
|
return u"%05d: %s (%s)" % (self.id, filename, caption)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
|
# заменим имя файла картинки
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
self.imFile.name = pytils.translit.slugify(self.szCaption.lower()) + str(Path(self.imFile.name).suffixes)
|
||||||
|
super(TbImages, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = u"КАРТИНКА"
|
||||||
|
verbose_name_plural = u"КАРТИНКИ"
|
||||||
|
ordering = ['id', ]
|
||||||
|
|
||||||
|
|
||||||
|
class TbOrigin(models.Model):
|
||||||
|
# ============================================================
|
||||||
|
# ТАБЛИЦА TbOrigin -- Источник, место откуда взята циатата, высказывание, изречение
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# | id -- id | INT(11) | PRIMARY KEY
|
||||||
|
# | szOrigin -- Источник | varchar(256) NULL
|
||||||
|
# | dtCreated -- Дата создания | datetime(6) NOT NULL
|
||||||
|
# | dtEdited -- Дата проверки | datetime(6) NOT NULL
|
||||||
|
# ============================================================
|
||||||
|
szOrigin = models.CharField(
|
||||||
|
max_length=256,
|
||||||
|
default=u"",
|
||||||
|
unique=True,
|
||||||
|
db_index=True,
|
||||||
|
verbose_name=u"Источник",
|
||||||
|
help_text=u"Ссылка или указание источника: книга, URL, просто что-то…"
|
||||||
|
)
|
||||||
|
dtCreated = models.DateTimeField(
|
||||||
|
db_index=True,
|
||||||
|
auto_now_add=True, # надо указать False при миграции, после вернуть в True
|
||||||
|
auto_now=False, # надо указать False при миграции, после вернуть в True
|
||||||
|
# для выполнения миграций нужно добавлять default, а после убрать (она не нужна)
|
||||||
|
# default=datetime.datetime.now(pytz.timezone(settings.TIME_ZONE)),
|
||||||
|
verbose_name=u"Дата создания"
|
||||||
|
)
|
||||||
|
dtEdited = models.DateTimeField(
|
||||||
|
db_index=True,
|
||||||
|
auto_now=True, # надо указать False при миграции, после вернуть в True
|
||||||
|
# для выполнения миграций нужно добавлять default, а после она не нужна
|
||||||
|
# default=datetime.datetime.now(pytz.timezone(settings.TIME_ZONE)),
|
||||||
|
verbose_name=u"Дата редактирования"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
origin = self.szOrigin
|
||||||
|
if len(origin) > 35:
|
||||||
|
origin = origin[:35] + u"…"
|
||||||
|
return u"%03d: %s" % (self.id, origin)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = u"ИСТОЧНИК"
|
||||||
|
verbose_name_plural = u"ИСТОЧНИКИ"
|
||||||
|
ordering = ['id', ]
|
||||||
|
|
||||||
|
|
||||||
|
class TbAuthor(models.Model):
|
||||||
|
# ============================================================
|
||||||
|
# ТАБЛИЦА TbAuthor -- Автор изречения или цитаты (dictum and quotes)
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# | id -- id | INT(11) | PRIMARY KEY
|
||||||
|
# | szAuthor -- Автор и, если необходимо, краткая справка | varchar(256) NOT NULL
|
||||||
|
# | szAuthorHTML -- Автор и... в HTML по правилам типографики | varchar(136) NULL
|
||||||
|
# | bIsChecked -- Проверен | tinyint(1) NOT NULL
|
||||||
|
# | iViewCounter -- Просмотры | int(10) UNSIGNED NOT NULL
|
||||||
|
# | dtCreated -- Дата создания | datetime(6) NOT NULL
|
||||||
|
# | dtEdited -- Дата проверки | datetime(6) NOT NULL
|
||||||
|
# ============================================================
|
||||||
|
szAuthor = models.CharField(
|
||||||
|
max_length=128,
|
||||||
|
default=u"",
|
||||||
|
unique=True,
|
||||||
|
db_index=True,
|
||||||
|
verbose_name=u"Автор",
|
||||||
|
help_text=u"Автор и, если необходимо, краткая справка"
|
||||||
|
)
|
||||||
|
szAuthorHTML = models.TextField(
|
||||||
|
default="",
|
||||||
|
blank=True,
|
||||||
|
verbose_name=u"Автор HTML",
|
||||||
|
help_text=u"Автор и, если необходимо, краткая справка<br />"
|
||||||
|
u"Свертано в HTML по правилам типографики <small>(рекламные URL вставляются тут)</small>"
|
||||||
|
)
|
||||||
|
bIsChecked = models.BooleanField(
|
||||||
|
default=True,
|
||||||
|
db_index=True,
|
||||||
|
verbose_name=u"Проверен",
|
||||||
|
help_text=u"Автор проверен."
|
||||||
|
)
|
||||||
|
tags = TaggableManager(
|
||||||
|
blank=True,
|
||||||
|
through=RuTaggedItem,
|
||||||
|
verbose_name=u"Теги",
|
||||||
|
help_text=u"Теги через запятую… Регистр не чувствителен… <b>Теги нужны для подстановки картинок и навигации<b>"
|
||||||
|
)
|
||||||
|
iViewCounter = models.PositiveIntegerField(
|
||||||
|
default=0,
|
||||||
|
verbose_name=u"◉",
|
||||||
|
help_text=u"Число просмотров Автора."
|
||||||
|
)
|
||||||
|
dtCreated = models.DateTimeField(
|
||||||
|
db_index=True,
|
||||||
|
auto_now_add=True, # надо указать False при миграции, после вернуть в True
|
||||||
|
auto_now=False, # надо указать False при миграции, после вернуть в True
|
||||||
|
# для выполнения миграций нужно добавлять default, а после убрать (она не нужна)
|
||||||
|
# default=datetime.datetime.now(pytz.timezone(settings.TIME_ZONE)),
|
||||||
|
verbose_name=u"Дата создания"
|
||||||
|
)
|
||||||
|
dtEdited = models.DateTimeField(
|
||||||
|
db_index=True,
|
||||||
|
auto_now=True, # надо указать False при миграции, после вернуть в True
|
||||||
|
# для выполнения миграций нужно добавлять default, а после она не нужна
|
||||||
|
# default=datetime.datetime.now(pytz.timezone(settings.TIME_ZONE)),
|
||||||
|
verbose_name=u"Дата редактирования"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
author = self.szAuthor
|
||||||
|
if len(author) > 25:
|
||||||
|
author = author[:25] + u"…"
|
||||||
|
return u"%04d: %s" % (self.id, author)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
http = urllib3.PoolManager()
|
||||||
|
# последовательно
|
||||||
|
# Используем типограф typus (https://github.com/byashimov/typus)
|
||||||
|
# Используем типограф Eugene Spearance (http://www.typograf.ru/)
|
||||||
|
# Используем типограф Муравьева (http://mdash.ru/api.v1.php)
|
||||||
|
self.szAuthor = ru_typus(self.szAuthor)
|
||||||
|
resp = http.request("POST",
|
||||||
|
"http://www.typograf.ru/webservice/",
|
||||||
|
fields={"text": self.szAuthor.encode('cp1251')})
|
||||||
|
self.szAuthorHTML = resp.data.decode('cp1251')
|
||||||
|
# print(self.szContentHTML)
|
||||||
|
resp = http.request("POST",
|
||||||
|
"http://mdash.ru/api.v1.php",
|
||||||
|
fields={"text": self.szAuthorHTML.encode('utf-8')})
|
||||||
|
self.szAuthorHTML = json.loads(resp.data)["result"]
|
||||||
|
# print(self.szContentHTML)
|
||||||
|
super(TbAuthor, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = u"АВТОР"
|
||||||
|
verbose_name_plural = u"АВТОРЫ"
|
||||||
|
ordering = ['id', ]
|
||||||
|
|
||||||
|
|
||||||
|
class TbDictumAndQuotes(models.Model):
|
||||||
|
# ============================================================
|
||||||
|
# ТАБЛИЦА TbDictQuot -- Изречения и Цитаты (dictum and quotes)
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# | id -- id | INT(11) | PRIMARY KEY
|
||||||
|
# | szIntro -- Вступление | varchar(256) NULL
|
||||||
|
# | szIntroHTML -- Вступление | (форматированное в HTML) varchar(136) NULL
|
||||||
|
# | szContent -- Высказывание | varchar(136) NOT NULL
|
||||||
|
# | szContentHtml -- Высказывание (Сформатированнон в HTML) | varchar(136) NULL
|
||||||
|
# | bIsChecked -- Проверен | tinyint(1) NOT NULL
|
||||||
|
# | kImages -- Ссылка на картинку в таблице TbImages |
|
||||||
|
# | iViewCounter -- Просмотры | int(10) UNSIGNED NOT NULL
|
||||||
|
# | dtCreated -- Дата создания | datetime(6) NOT NULL
|
||||||
|
# | dtEdited -- Дата проверки | datetime(6) NOT NULL
|
||||||
|
# ============================================================
|
||||||
|
szIntro = models.CharField(
|
||||||
|
max_length=256,
|
||||||
|
default=None,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=u"Вступление",
|
||||||
|
help_text=u"Не обязательно. Вступление перед цитатой."
|
||||||
|
)
|
||||||
|
szIntroHTML = models.TextField(
|
||||||
|
default="",
|
||||||
|
blank=True,
|
||||||
|
verbose_name=u"Вступление HTML",
|
||||||
|
help_text=u"Автор и, если необходимо, краткая справка<br />"
|
||||||
|
u"Вступление перед цитатой, в HTML по правилам типографики</small>"
|
||||||
|
)
|
||||||
|
szContent = models.TextField(
|
||||||
|
max_length=256,
|
||||||
|
default="",
|
||||||
|
verbose_name=u"Высказывание",
|
||||||
|
help_text=u"Не обязательно. Вступление перед цитатой."
|
||||||
|
)
|
||||||
|
szContentHTML = models.TextField(
|
||||||
|
default="",
|
||||||
|
blank=True,
|
||||||
|
verbose_name=u"Высказывание HTML",
|
||||||
|
help_text=u"<b>Высказывание Крылатое</b> -- крылатое, пародоксальное и все такое"
|
||||||
|
)
|
||||||
|
kAuthor = models.ForeignKey(
|
||||||
|
TbAuthor,
|
||||||
|
default=None,
|
||||||
|
blank=True,
|
||||||
|
on_delete=models.DO_NOTHING,
|
||||||
|
verbose_name=u"Автор",
|
||||||
|
help_text=u"Автор изречения или цитаты <b>(не обязательно, но желательно)</b>"
|
||||||
|
)
|
||||||
|
kOrigin = models.ForeignKey(
|
||||||
|
TbOrigin,
|
||||||
|
default=None,
|
||||||
|
blank=True,
|
||||||
|
on_delete=models.DO_NOTHING,
|
||||||
|
verbose_name=u"Источник",
|
||||||
|
help_text=u"Откуда взята циатата, высказывание, изречение <b>(не обязательно, но желательно)</b>"
|
||||||
|
)
|
||||||
|
kImages = models.ForeignKey(
|
||||||
|
TbImages,
|
||||||
|
default=None,
|
||||||
|
blank=True,
|
||||||
|
on_delete=models.DO_NOTHING,
|
||||||
|
verbose_name=u"Картинка",
|
||||||
|
help_text=u"Ссылка на картинку, в табличке картинок <b>(не обязательно)</b><br />"
|
||||||
|
u"<small>если нужна именно данная картинка, а не выбранная автоматически</small>"
|
||||||
|
)
|
||||||
|
imFileOG = models.ImageField(
|
||||||
|
max_length=136,
|
||||||
|
upload_to="img2og",
|
||||||
|
default=u"",
|
||||||
|
blank=True,
|
||||||
|
verbose_name=u"OG-image",
|
||||||
|
help_text=u"Картинка для социальной сети <b>(будет создана автоматически)</b>.<br />"
|
||||||
|
u"<small>Файл с картинкой (png).<small>"
|
||||||
|
)
|
||||||
|
iViewCounter = models.PositiveIntegerField(
|
||||||
|
default=0,
|
||||||
|
db_index=True,
|
||||||
|
verbose_name=u"◉",
|
||||||
|
help_text=u"Число просмотров высказывания."
|
||||||
|
)
|
||||||
|
tags = TaggableManager(
|
||||||
|
blank=True,
|
||||||
|
through=RuTaggedItem,
|
||||||
|
verbose_name=u"Теги",
|
||||||
|
help_text=u"Теги через запятую… Регистр не чувствителен… <b>Теги нужны для подстановки картинок и навигации<b>"
|
||||||
|
)
|
||||||
|
dtCreated = models.DateTimeField(
|
||||||
|
db_index=True,
|
||||||
|
auto_now_add=True, # надо указать False при миграции, после вернуть в True
|
||||||
|
auto_now=False, # надо указать False при миграции, после вернуть в True
|
||||||
|
# для выполнения миграций нужно добавлять default, а после убрать (она не нужна)
|
||||||
|
# default=datetime.datetime.now(pytz.timezone(settings.TIME_ZONE)),
|
||||||
|
verbose_name=u"Дата создания"
|
||||||
|
)
|
||||||
|
dtEdited = models.DateTimeField(
|
||||||
|
db_index=True,
|
||||||
|
auto_now=True, # надо указать False при миграции, после вернуть в True
|
||||||
|
# для выполнения миграций нужно добавлять default, а после она не нужна
|
||||||
|
# default=datetime.datetime.now(pytz.timezone(settings.TIME_ZONE)),
|
||||||
|
verbose_name=u"Дата редактирования"
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
intro = self.szIntro
|
||||||
|
if len(intro) > 35:
|
||||||
|
intro = intro[:35] + u"…"
|
||||||
|
content = self.szContent
|
||||||
|
if len(content) > 55:
|
||||||
|
content = content[:55] + u"…"
|
||||||
|
return u"%05d: %s :: %s" % (self.id, intro, content)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.__str__()
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
http = urllib3.PoolManager()
|
||||||
|
# последовательно
|
||||||
|
# Используем типограф typus (https://github.com/byashimov/typus)
|
||||||
|
# Используем типограф Eugene Spearance (http://www.typograf.ru/)
|
||||||
|
# Используем типограф Муравьева (http://mdash.ru/api.v1.php)
|
||||||
|
if self.szIntro != "" and self.szIntro != ru_typus(self.szIntro):
|
||||||
|
# сравнение self.szIntro != ru_typus(self.szIntro) нужно для избежания повторных обращений
|
||||||
|
# к типографам при обновлении щетчиков просмотра
|
||||||
|
self.szIntro = ru_typus(self.szIntro)
|
||||||
|
resp = http.request("POST",
|
||||||
|
"http://www.typograf.ru/webservice/",
|
||||||
|
fields={"text": self.szIntro.replace("\u202f", " ").replace("\u2009", " ").encode('cp1251')})
|
||||||
|
self.szIntroHTML = resp.data.decode('cp1251')
|
||||||
|
# print(self.szIntroHTML)
|
||||||
|
resp = http.request("POST",
|
||||||
|
"http://mdash.ru/api.v1.php",
|
||||||
|
fields={"text": self.szIntroHTML.encode('utf-8')})
|
||||||
|
self.szIntroHTML = json.loads(resp.data)["result"]
|
||||||
|
# print(self.szIntroHTML)
|
||||||
|
else:
|
||||||
|
self.szIntroHTML = ""
|
||||||
|
if self.szContent != ru_typus(self.szContent):
|
||||||
|
# self.szContent != ru_typus(self.szContent) нужно для избежания повторных обращений
|
||||||
|
# к типографам при обновлении щетчиков просмотра
|
||||||
|
self.szContent = ru_typus(self.szContent)
|
||||||
|
resp = http.request("POST",
|
||||||
|
"http://www.typograf.ru/webservice/",
|
||||||
|
fields={"text": self.szContent.replace("\u202f", " ").replace("\u2009", " ").encode('cp1251')})
|
||||||
|
self.szContentHTML = resp.data.decode('cp1251')
|
||||||
|
print(self.szContentHTML)
|
||||||
|
resp = http.request("POST",
|
||||||
|
"http://mdash.ru/api.v1.php",
|
||||||
|
fields={"text": self.szContentHTML.encode('utf-8')})
|
||||||
|
self.szContentHTML = json.loads(resp.data)["result"]
|
||||||
|
# print(self.szContentHTML)
|
||||||
|
super(TbDictumAndQuotes, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = u"ВЫСКАЗЫВАНИЕ"
|
||||||
|
verbose_name_plural = u"ВЫСКАЗЫВАНИЯ"
|
||||||
|
ordering = ['id', ]
|
4
dicquo/web/tests.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
89
dicquo/web/views.py
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
__author__ = "Sergei Erjemin"
|
||||||
|
__copyright__ = "Copyright 2020, Sergei Erjemin"
|
||||||
|
__credits__ = ["Sergei Erjemin", ]
|
||||||
|
__license__ = "GPL"
|
||||||
|
__version__ = "0.0.1"
|
||||||
|
__maintainer__ = "Sergei Erjemin"
|
||||||
|
__email__ = "erjemin@gmail.com"
|
||||||
|
__status__ = "in progress"
|
||||||
|
|
||||||
|
from django.shortcuts import render
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
import time
|
||||||
|
import hashlib
|
||||||
|
import random
|
||||||
|
import pytils
|
||||||
|
from taggit.models import Tag
|
||||||
|
from web.models import TbOrigin, TbDictumAndQuotes, TbImages, TbAuthor
|
||||||
|
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
|
def for_dq(dq):
|
||||||
|
to_template = {}
|
||||||
|
num = int(hashlib.blake2s(dq.szContent.encode("utf-8"), digest_size=1).hexdigest(), 16)
|
||||||
|
clr = sorted([num / 2, num / 3, num / 5, num / 7, num / 11, num / 1.5], key=lambda A: random.random())
|
||||||
|
to_template.update({'CLR': clr})
|
||||||
|
to_template.update({'DQ': dq})
|
||||||
|
try:
|
||||||
|
au = TbAuthor.objects.get(id=dq.kAuthor_id)
|
||||||
|
to_template.update({'AUTHOR': au})
|
||||||
|
tags = au.tags.names()
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
tags = dq.tags.names()
|
||||||
|
tag_and_slug = []
|
||||||
|
for i in tags:
|
||||||
|
tag_and_slug.append({"name": i, "slug": pytils.translit.slugify(i.lower())[:128]})
|
||||||
|
to_template.update({'TAGS': sorted(tag_and_slug, key=lambda x: x["name"])}) # tag_and_slug
|
||||||
|
if dq.kImages_id is None:
|
||||||
|
if len(tags) != 0:
|
||||||
|
try:
|
||||||
|
tagged_image = TbImages.objects.filter(tags__name__in=tags).order_by('?')[0]
|
||||||
|
to_template.update({'IMAGE': tagged_image.imFile})
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
to_template.update({'IMAGE': dq.kImages.imFile})
|
||||||
|
dq.iViewCounter += 1
|
||||||
|
dq.save()
|
||||||
|
dq_next = TbDictumAndQuotes.objects.exclude(id=dq.id).order_by('?').first()
|
||||||
|
to_template.update({"NEXT": dq_next.id})
|
||||||
|
to_template.update({"NEXT_TXT": pytils.translit.slugify(dq_next.szContent.lower()[:120])})
|
||||||
|
return to_template
|
||||||
|
|
||||||
|
|
||||||
|
def by_id( request, dq_id):
|
||||||
|
t_start = time.process_time()
|
||||||
|
template = "index.html" # шаблон
|
||||||
|
dq = TbDictumAndQuotes.objects.get(id=dq_id)
|
||||||
|
to_template = for_dq(dq)
|
||||||
|
# пероверка, что посетитель согласился со сбором даных через cookies
|
||||||
|
if request.COOKIES.get('cookie_accept'):
|
||||||
|
to_template.update({'cookie_accept': 1})
|
||||||
|
to_template.update({'ticks': float(time.process_time() - t_start)})
|
||||||
|
response = render(request, template, to_template)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
def index(request):
|
||||||
|
t_start = time.process_time()
|
||||||
|
# проверка на аутентификацию
|
||||||
|
# if not request.user.is_authenticated():
|
||||||
|
# return HttpResponseRedirect("/access")
|
||||||
|
template = "index.html" # шаблон
|
||||||
|
dq_ = TbDictumAndQuotes.objects.order_by('?')
|
||||||
|
if request.GET.get('tag'):
|
||||||
|
dq = dq_.filter(kAuthor__tags__slug__in=[request.GET['tag']]).first()
|
||||||
|
if dq is None:
|
||||||
|
dq = dq_.filter(tags__slug__in=[request.GET['tag']]).first()
|
||||||
|
if dq is None:
|
||||||
|
dq = dq_.first()
|
||||||
|
else:
|
||||||
|
dq = dq_.first()
|
||||||
|
to_template = for_dq(dq)
|
||||||
|
# пероверка, что посетитель согласился со сбором даных через cookies
|
||||||
|
if request.COOKIES.get('cookie_accept'):
|
||||||
|
to_template.update({'cookie_accept': 1})
|
||||||
|
to_template.update({'ticks': float(time.process_time() - t_start)})
|
||||||
|
response = render(request, template, to_template)
|
||||||
|
return response
|
37
passenger_wsgi.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#!/home/eserg/dq.cube2.ru/env/bin/python3
|
||||||
|
|
||||||
|
import sys, os
|
||||||
|
INTERP = "/home/eserg/dq.cube2.ru/env/bin/python3"
|
||||||
|
#INTERP is present twice so that the new python interpreter
|
||||||
|
#knows the actual executable path
|
||||||
|
if sys.executable != INTERP:
|
||||||
|
os.execl(INTERP, INTERP, *sys.argv)
|
||||||
|
|
||||||
|
cwd = os.getcwd()
|
||||||
|
sys.path.append(cwd)
|
||||||
|
sys.path.append(cwd + '/dicquo') #You must add your project here
|
||||||
|
|
||||||
|
sys.path.insert(0,cwd+'/env/bin')
|
||||||
|
sys.path.insert(0,cwd+'/env/lib/python3.8/site-packages')
|
||||||
|
|
||||||
|
os.environ['DJANGO_SETTINGS_MODULE'] = "dicquo.settings"
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
application = get_wsgi_application()
|
||||||
|
|
||||||
|
|
||||||
|
##!/usr/bin/env python
|
||||||
|
#import sys, os
|
||||||
|
#cwd = os.getcwd()
|
||||||
|
#sys.path.append(cwd)
|
||||||
|
#sys.path.append(cwd + '/dicquo')
|
||||||
|
|
||||||
|
##Switch to new python
|
||||||
|
##if sys.version < "2.7.8": os.execl(cwd+"/env/bin/python", "python2.7", *sys.argv)
|
||||||
|
|
||||||
|
#sys.path.insert(0,cwd+'/env/bin')
|
||||||
|
#sys.path.insert(0,cwd+'/env/lib/python3.8/site-packages/django')
|
||||||
|
#sys.path.insert(0,cwd+'/env/lib/python3.8/site-packages')
|
||||||
|
|
||||||
|
#os.environ['DJANGO_SETTINGS_MODULE'] = "dicquo.settings"
|
||||||
|
#from django.core.wsgi import get_wsgi_application
|
||||||
|
#application = get_wsgi_application()
|
BIN
public/favicon.gif
Normal file
After ![]() (image error) Size: 4.5 KiB |
BIN
public/favicon.ico
Normal file
After Width: 121px | Height: 110px | Size: 54 KiB |
53
public/favicon.svg
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<!-- Creator: CorelDRAW X6 -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="50px" height="46px" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||||
|
viewBox="0 0 1512 1386"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<defs>
|
||||||
|
<style type="text/css">
|
||||||
|
<![CDATA[
|
||||||
|
.fil6 {fill:#FFD900}
|
||||||
|
.fil2 {fill:#AE4A84;fill-rule:nonzero}
|
||||||
|
.fil3 {fill:url(#id0)}
|
||||||
|
.fil4 {fill:url(#id1)}
|
||||||
|
.fil1 {fill:url(#id2)}
|
||||||
|
.fil0 {fill:url(#id3)}
|
||||||
|
.fil5 {fill:url(#id4)}
|
||||||
|
]]>
|
||||||
|
</style>
|
||||||
|
<linearGradient id="id0" gradientUnits="userSpaceOnUse" x1="1476.52" y1="1395.4" x2="1034.86" y2="872.762">
|
||||||
|
<stop offset="0" style="stop-color:#EF7F1A"/>
|
||||||
|
<stop offset="0.34902" style="stop-color:#FFED00"/>
|
||||||
|
<stop offset="0.94902" style="stop-color:#CC6F3C"/>
|
||||||
|
<stop offset="1" style="stop-color:#CC6F3C"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="id1" gradientUnits="userSpaceOnUse" x1="1023.98" y1="1127.45" x2="575.667" y2="596.929">
|
||||||
|
<stop offset="0" style="stop-color:#EF7F1A"/>
|
||||||
|
<stop offset="0.0588235" style="stop-color:#CC6F3C"/>
|
||||||
|
<stop offset="0.670588" style="stop-color:#FFED00"/>
|
||||||
|
<stop offset="1" style="stop-color:#CC6F3C"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="id2" gradientUnits="userSpaceOnUse" xlink:href="#id0" x1="1398.9" y1="1160.31" x2="613.548" y2="230.976">
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="id3" gradientUnits="userSpaceOnUse" xlink:href="#id0" x1="805.595" y1="948.571" x2="21.0238" y2="20.1429">
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="id4" gradientUnits="userSpaceOnUse" x1="381.071" y1="958.69" x2="89.0714" y2="613.119">
|
||||||
|
<stop offset="0" style="stop-color:#EF7F1A"/>
|
||||||
|
<stop offset="0.831373" style="stop-color:#FFED00"/>
|
||||||
|
<stop offset="0.94902" style="stop-color:#CC6F3C"/>
|
||||||
|
<stop offset="1" style="stop-color:#CC6F3C"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g id="Layer_x0020_1">
|
||||||
|
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||||
|
<path class="fil0" d="M746 677l-341 293c21,-19 37,-44 49,-75 14,-37 20,-83 20,-137 0,-47 -6,-92 -19,-135 -13,-43 -32,-82 -58,-118 -26,-36 -57,-67 -95,-95 -38,-27 -84,-50 -139,-68l-136 -44c-6,-2 -12,-2 -18,1 -1,0 -2,1 -3,2l345 -296c1,-1 2,-2 4,-2 6,-3 12,-3 18,-1l136 44c55,18 101,41 139,68 38,27 69,59 95,95 25,36 45,75 58,118 13,43 19,88 19,135 0,54 -7,100 -20,137 -12,34 -30,60 -53,79z"/>
|
||||||
|
<path class="fil1" d="M1005 1173c12,-10 22,-22 30,-35 10,-16 18,-34 25,-54 6,-20 11,-41 14,-65 3,-23 4,-48 4,-73 0,-50 -6,-97 -17,-141 -12,-44 -29,-83 -51,-119 -23,-35 -51,-66 -85,-92 -34,-26 -74,-47 -119,-62 -48,-16 -90,-22 -126,-17 -36,4 -66,17 -91,40l345 -295c24,-22 54,-34 90,-39 36,-4 78,2 126,17 45,15 85,35 119,62 34,26 63,57 85,92 23,35 40,75 51,119 11,44 17,90 17,141 0,26 -2,50 -4,73 -3,23 -8,45 -14,65 -6,20 -15,38 -25,54 -9,14 -20,27 -33,37l-341 292z"/>
|
||||||
|
<path class="fil2" d="M409 739c0,-38 -5,-74 -14,-109 -10,-35 -24,-68 -44,-97 -20,-30 -45,-56 -75,-79 -30,-23 -69,-42 -116,-58l-99 -33 0 538 100 33c44,14 81,21 111,20 30,-1 56,-9 76,-25 20,-16 35,-40 46,-71 10,-31 15,-71 15,-118zm65 18c0,54 -7,100 -20,137 -14,37 -34,65 -60,84 -27,19 -59,30 -98,31 -39,1 -86,-7 -140,-24l-127 -42c-6,-2 -12,-6 -18,-13 -6,-7 -10,-16 -10,-28l0 -583c0,-12 3,-19 10,-22 6,-3 12,-3 18,-1l136 44c55,18 101,41 139,68 38,27 69,59 95,95 25,36 45,75 58,118 13,43 19,88 19,135z"/>
|
||||||
|
<path class="fil3" d="M1343 884l-333 285c-3,3 -7,6 -10,9 26,30 49,54 68,73 20,18 36,33 48,43 13,10 22,18 29,23 7,5 12,9 15,13 3,4 5,8 6,13 1,5 2,10 2,17 0,5 0,10 -1,13 -1,3 -2,6 -4,8 -1,1 -1,1 -2,2l344 -294c1,0 1,-1 2,-2 2,-2 3,-5 4,-8 1,-4 1,-8 1,-13 0,-7 -1,-12 -2,-17 -1,-5 -3,-9 -6,-13 -3,-4 -8,-8 -15,-13 -7,-5 -17,-13 -29,-23 -13,-10 -29,-25 -48,-43 -20,-19 -42,-43 -68,-73z"/>
|
||||||
|
<path class="fil2" d="M1015 931c0,-38 -4,-75 -11,-111 -7,-36 -19,-69 -36,-99 -17,-30 -38,-57 -66,-80 -27,-23 -61,-41 -102,-54 -40,-13 -74,-17 -101,-11 -28,6 -50,18 -67,38 -17,19 -29,44 -37,75 -8,31 -11,65 -11,101 0,39 3,77 10,113 7,36 18,69 35,99 16,30 38,57 66,80 27,23 62,41 103,55 41,13 75,17 103,11 28,-6 50,-19 67,-39 17,-20 29,-45 36,-76 7,-31 11,-65 11,-102zm153 430c0,5 0,10 -1,13 -1,3 -2,6 -4,8 -2,2 -3,3 -5,3 -2,1 -3,0 -5,0 -4,-1 -12,-7 -26,-16 -13,-10 -29,-22 -48,-39 -19,-16 -39,-35 -61,-57 -22,-22 -44,-46 -66,-72 -17,6 -39,9 -65,9 -27,0 -58,-6 -94,-18 -48,-16 -89,-37 -124,-63 -35,-27 -63,-57 -85,-93 -22,-35 -38,-75 -49,-119 -10,-44 -16,-91 -16,-142 0,-51 6,-95 18,-132 12,-37 29,-67 53,-88 24,-21 53,-34 89,-38 36,-4 78,2 126,17 45,15 85,35 119,62 34,26 63,57 85,92 23,35 40,75 51,119 11,44 17,90 17,141 0,26 -2,50 -4,73 -3,23 -8,45 -14,65 -6,20 -15,38 -25,54 -10,16 -22,29 -36,40 26,30 49,54 68,73 20,18 36,33 48,43 13,10 22,18 29,23 7,5 12,9 15,13 3,4 5,8 6,13 1,5 2,10 2,17z"/>
|
||||||
|
<path class="fil4" d="M1015 931c0,-38 -4,-75 -11,-111 -7,-36 -19,-69 -36,-99 -17,-30 -38,-57 -66,-80 -27,-23 -61,-41 -102,-54 -40,-13 -74,-17 -101,-11 -28,6 -50,18 -67,38 -17,19 -29,44 -37,75 -8,31 -11,65 -11,101 0,39 3,77 10,113 7,36 18,69 35,99 16,30 38,57 66,80 27,23 62,41 103,55 41,13 75,17 103,11 28,-6 50,-19 67,-39 17,-20 29,-45 36,-76 7,-31 11,-65 11,-102z"/>
|
||||||
|
<path class="fil5" d="M61 900l330 -283c1,4 2,8 4,12 9,35 14,72 14,109 0,48 -5,87 -15,118 -10,31 -25,55 -46,71 -20,16 -45,24 -76,25 -31,1 -67,-6 -111,-20l-100 -33z"/>
|
||||||
|
<path class="fil6" d="M61 900l330 -283c-9,-31 -23,-59 -40,-85 -20,-30 -45,-56 -75,-79 -30,-23 -69,-42 -116,-58l-99 -33 0 538z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After (image error) Size: 5.5 KiB |
BIN
public/media/img2/boris-grebenschikov-02.jpg
Normal file
After ![]() (image error) Size: 49 KiB |
BIN
public/media/img2/boris-grebenschikov.png
Normal file
After ![]() (image error) Size: 348 KiB |
BIN
public/media/img2/dzhonni-kesh-1954.jpg
Normal file
After ![]() (image error) Size: 32 KiB |
BIN
public/media/img2/jim-morrison-01.jpg
Normal file
After ![]() (image error) Size: 76 KiB |
BIN
public/media/img2/leonard-cohen-01.jpg
Normal file
After ![]() (image error) Size: 1.5 MiB |
BIN
public/media/img2/leonard-cohen-02.jpg
Normal file
After ![]() (image error) Size: 72 KiB |
BIN
public/media/img2/lou-reed-001.jpg
Normal file
After ![]() (image error) Size: 53 KiB |
BIN
public/media/img2/nick-cave-01.jpg
Normal file
After ![]() (image error) Size: 55 KiB |
BIN
public/media/img2/nick-cave-02.Cave__.2020_.png
Normal file
After ![]() (image error) Size: 240 KiB |
BIN
public/media/img2/sergej-kuryohin-01.jpg
Normal file
After ![]() (image error) Size: 212 KiB |
BIN
public/media/img2/sergej-kuryohin-02.jpg
Normal file
After ![]() (image error) Size: 156 KiB |
7
public/robots.txt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# DicQuo
|
||||||
|
User-Agent: *
|
||||||
|
Allow: /
|
||||||
|
Disallow:
|
||||||
|
Host: dq.cube2.ru
|
||||||
|
Sitemap: https://dq.cube2.ru/sitemap.xml
|
||||||
|
|
134
public/static/css/dicquo.css
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
@charset "utf-8";
|
||||||
|
|
||||||
|
.tags{
|
||||||
|
color: silver;
|
||||||
|
font-size:1.5vh;
|
||||||
|
line-height:1.9vh;
|
||||||
|
padding-top: 7vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************
|
||||||
|
* Настройки для анимирования цвета ссылок:
|
||||||
|
* рецепт взят из: https://habr.com/ru/company/ruvds/blog/491702/
|
||||||
|
*****************************************************************/
|
||||||
|
.tags a {
|
||||||
|
text-decoration: none;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 0.5ex;
|
||||||
|
display: inline-block;
|
||||||
|
color: white;
|
||||||
|
border-bottom: dotted 1px silver;
|
||||||
|
/* градиент для цвета ссылки */
|
||||||
|
background: linear-gradient(to right, rgba(255,255,255,0.9) 40%, slategray, silver, lightyellow 50%, rgba(255,255,255,0.4));
|
||||||
|
/* обрезка градиента */
|
||||||
|
background-clip: initial;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-size: 250% 100%;
|
||||||
|
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=30)";
|
||||||
|
background-position: 100%;
|
||||||
|
/* плавное позиионирование градиента */
|
||||||
|
transition: background-position 0.65s ease;
|
||||||
|
margin-right: 2vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags a:hover {
|
||||||
|
color: white;
|
||||||
|
background-position: 0 100%;
|
||||||
|
border-bottom: solid 1px white;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[name="cookies_accept"] {
|
||||||
|
font-family:'Roboto', 'Lucida Grande', Verdana, Arial, sans-serif;
|
||||||
|
position:fixed;
|
||||||
|
bottom: 0; left: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 2vh 2vw;
|
||||||
|
color: black;
|
||||||
|
background-color: gray;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
div[name="cookies_accept"] button {
|
||||||
|
padding:0.5vh 0.5vw;
|
||||||
|
background: silver;
|
||||||
|
color: black;
|
||||||
|
margin-left: 2vw;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logo {
|
||||||
|
margin-top:1vh;
|
||||||
|
filter: alpha(Opacity=75); /* Полупрозрачность для IE */
|
||||||
|
opacity: 0.75;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#logo a {
|
||||||
|
border: none;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table { width: 80%; }
|
||||||
|
|
||||||
|
#menu {
|
||||||
|
display: none;
|
||||||
|
color: silver;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mm {
|
||||||
|
text-decoration: none;
|
||||||
|
color: silver;
|
||||||
|
}
|
||||||
|
|
||||||
|
#image {
|
||||||
|
width: 30vw;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#image > center > div {
|
||||||
|
width: 22vw;
|
||||||
|
height: 22vw;
|
||||||
|
padding:0.5vw;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#image > center > div > div {
|
||||||
|
border-radius:50%;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#image > center > div > div > img {
|
||||||
|
width: auto;
|
||||||
|
height: 22vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
#author {
|
||||||
|
color: silver;
|
||||||
|
font-size: 3.5vh;
|
||||||
|
line-height: 4vh;
|
||||||
|
text-align: right;
|
||||||
|
padding-top: 4vh;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
#info {
|
||||||
|
color: silver;
|
||||||
|
font-size: 2.5vh;
|
||||||
|
line-height: 3vh;
|
||||||
|
padding-bottom: 2vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bb {
|
||||||
|
color: whitesmoke;
|
||||||
|
font-size: 4.5vh;
|
||||||
|
line-height: 5vh
|
||||||
|
}
|
||||||
|
|
||||||
|
#next { float: right; }
|
||||||
|
|
||||||
|
#next a { border-bottom: none; }
|
BIN
public/static/img/cubex.png
Normal file
After ![]() (image error) Size: 299 KiB |
BIN
public/static/img/favicon.gif
Normal file
After ![]() (image error) Size: 4.5 KiB |
BIN
public/static/img/favicon.ico
Normal file
After Width: 121px | Height: 110px | Size: 54 KiB |
BIN
public/static/img/favicon.png
Normal file
After ![]() (image error) Size: 8.8 KiB |
BIN
public/static/img/greyzz.png
Normal file
After ![]() (image error) Size: 6.0 KiB |
53
public/static/svgs/dq-logo.svg
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<!-- Creator: CorelDRAW X6 -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="50px" height="46px" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||||
|
viewBox="0 0 1512 1386"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<defs>
|
||||||
|
<style type="text/css">
|
||||||
|
<![CDATA[
|
||||||
|
.fil6 {fill:#FFD900}
|
||||||
|
.fil2 {fill:#AE4A84;fill-rule:nonzero}
|
||||||
|
.fil3 {fill:url(#id0)}
|
||||||
|
.fil4 {fill:url(#id1)}
|
||||||
|
.fil1 {fill:url(#id2)}
|
||||||
|
.fil0 {fill:url(#id3)}
|
||||||
|
.fil5 {fill:url(#id4)}
|
||||||
|
]]>
|
||||||
|
</style>
|
||||||
|
<linearGradient id="id0" gradientUnits="userSpaceOnUse" x1="1476.52" y1="1395.4" x2="1034.86" y2="872.762">
|
||||||
|
<stop offset="0" style="stop-color:#EF7F1A"/>
|
||||||
|
<stop offset="0.34902" style="stop-color:#FFED00"/>
|
||||||
|
<stop offset="0.94902" style="stop-color:#CC6F3C"/>
|
||||||
|
<stop offset="1" style="stop-color:#CC6F3C"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="id1" gradientUnits="userSpaceOnUse" x1="1023.98" y1="1127.45" x2="575.667" y2="596.929">
|
||||||
|
<stop offset="0" style="stop-color:#EF7F1A"/>
|
||||||
|
<stop offset="0.0588235" style="stop-color:#CC6F3C"/>
|
||||||
|
<stop offset="0.670588" style="stop-color:#FFED00"/>
|
||||||
|
<stop offset="1" style="stop-color:#CC6F3C"/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="id2" gradientUnits="userSpaceOnUse" xlink:href="#id0" x1="1398.9" y1="1160.31" x2="613.548" y2="230.976">
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="id3" gradientUnits="userSpaceOnUse" xlink:href="#id0" x1="805.595" y1="948.571" x2="21.0238" y2="20.1429">
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="id4" gradientUnits="userSpaceOnUse" x1="381.071" y1="958.69" x2="89.0714" y2="613.119">
|
||||||
|
<stop offset="0" style="stop-color:#EF7F1A"/>
|
||||||
|
<stop offset="0.831373" style="stop-color:#FFED00"/>
|
||||||
|
<stop offset="0.94902" style="stop-color:#CC6F3C"/>
|
||||||
|
<stop offset="1" style="stop-color:#CC6F3C"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g id="Layer_x0020_1">
|
||||||
|
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||||
|
<path class="fil0" d="M746 677l-341 293c21,-19 37,-44 49,-75 14,-37 20,-83 20,-137 0,-47 -6,-92 -19,-135 -13,-43 -32,-82 -58,-118 -26,-36 -57,-67 -95,-95 -38,-27 -84,-50 -139,-68l-136 -44c-6,-2 -12,-2 -18,1 -1,0 -2,1 -3,2l345 -296c1,-1 2,-2 4,-2 6,-3 12,-3 18,-1l136 44c55,18 101,41 139,68 38,27 69,59 95,95 25,36 45,75 58,118 13,43 19,88 19,135 0,54 -7,100 -20,137 -12,34 -30,60 -53,79z"/>
|
||||||
|
<path class="fil1" d="M1005 1173c12,-10 22,-22 30,-35 10,-16 18,-34 25,-54 6,-20 11,-41 14,-65 3,-23 4,-48 4,-73 0,-50 -6,-97 -17,-141 -12,-44 -29,-83 -51,-119 -23,-35 -51,-66 -85,-92 -34,-26 -74,-47 -119,-62 -48,-16 -90,-22 -126,-17 -36,4 -66,17 -91,40l345 -295c24,-22 54,-34 90,-39 36,-4 78,2 126,17 45,15 85,35 119,62 34,26 63,57 85,92 23,35 40,75 51,119 11,44 17,90 17,141 0,26 -2,50 -4,73 -3,23 -8,45 -14,65 -6,20 -15,38 -25,54 -9,14 -20,27 -33,37l-341 292z"/>
|
||||||
|
<path class="fil2" d="M409 739c0,-38 -5,-74 -14,-109 -10,-35 -24,-68 -44,-97 -20,-30 -45,-56 -75,-79 -30,-23 -69,-42 -116,-58l-99 -33 0 538 100 33c44,14 81,21 111,20 30,-1 56,-9 76,-25 20,-16 35,-40 46,-71 10,-31 15,-71 15,-118zm65 18c0,54 -7,100 -20,137 -14,37 -34,65 -60,84 -27,19 -59,30 -98,31 -39,1 -86,-7 -140,-24l-127 -42c-6,-2 -12,-6 -18,-13 -6,-7 -10,-16 -10,-28l0 -583c0,-12 3,-19 10,-22 6,-3 12,-3 18,-1l136 44c55,18 101,41 139,68 38,27 69,59 95,95 25,36 45,75 58,118 13,43 19,88 19,135z"/>
|
||||||
|
<path class="fil3" d="M1343 884l-333 285c-3,3 -7,6 -10,9 26,30 49,54 68,73 20,18 36,33 48,43 13,10 22,18 29,23 7,5 12,9 15,13 3,4 5,8 6,13 1,5 2,10 2,17 0,5 0,10 -1,13 -1,3 -2,6 -4,8 -1,1 -1,1 -2,2l344 -294c1,0 1,-1 2,-2 2,-2 3,-5 4,-8 1,-4 1,-8 1,-13 0,-7 -1,-12 -2,-17 -1,-5 -3,-9 -6,-13 -3,-4 -8,-8 -15,-13 -7,-5 -17,-13 -29,-23 -13,-10 -29,-25 -48,-43 -20,-19 -42,-43 -68,-73z"/>
|
||||||
|
<path class="fil2" d="M1015 931c0,-38 -4,-75 -11,-111 -7,-36 -19,-69 -36,-99 -17,-30 -38,-57 -66,-80 -27,-23 -61,-41 -102,-54 -40,-13 -74,-17 -101,-11 -28,6 -50,18 -67,38 -17,19 -29,44 -37,75 -8,31 -11,65 -11,101 0,39 3,77 10,113 7,36 18,69 35,99 16,30 38,57 66,80 27,23 62,41 103,55 41,13 75,17 103,11 28,-6 50,-19 67,-39 17,-20 29,-45 36,-76 7,-31 11,-65 11,-102zm153 430c0,5 0,10 -1,13 -1,3 -2,6 -4,8 -2,2 -3,3 -5,3 -2,1 -3,0 -5,0 -4,-1 -12,-7 -26,-16 -13,-10 -29,-22 -48,-39 -19,-16 -39,-35 -61,-57 -22,-22 -44,-46 -66,-72 -17,6 -39,9 -65,9 -27,0 -58,-6 -94,-18 -48,-16 -89,-37 -124,-63 -35,-27 -63,-57 -85,-93 -22,-35 -38,-75 -49,-119 -10,-44 -16,-91 -16,-142 0,-51 6,-95 18,-132 12,-37 29,-67 53,-88 24,-21 53,-34 89,-38 36,-4 78,2 126,17 45,15 85,35 119,62 34,26 63,57 85,92 23,35 40,75 51,119 11,44 17,90 17,141 0,26 -2,50 -4,73 -3,23 -8,45 -14,65 -6,20 -15,38 -25,54 -10,16 -22,29 -36,40 26,30 49,54 68,73 20,18 36,33 48,43 13,10 22,18 29,23 7,5 12,9 15,13 3,4 5,8 6,13 1,5 2,10 2,17z"/>
|
||||||
|
<path class="fil4" d="M1015 931c0,-38 -4,-75 -11,-111 -7,-36 -19,-69 -36,-99 -17,-30 -38,-57 -66,-80 -27,-23 -61,-41 -102,-54 -40,-13 -74,-17 -101,-11 -28,6 -50,18 -67,38 -17,19 -29,44 -37,75 -8,31 -11,65 -11,101 0,39 3,77 10,113 7,36 18,69 35,99 16,30 38,57 66,80 27,23 62,41 103,55 41,13 75,17 103,11 28,-6 50,-19 67,-39 17,-20 29,-45 36,-76 7,-31 11,-65 11,-102z"/>
|
||||||
|
<path class="fil5" d="M61 900l330 -283c1,4 2,8 4,12 9,35 14,72 14,109 0,48 -5,87 -15,118 -10,31 -25,55 -46,71 -20,16 -45,24 -76,25 -31,1 -67,-6 -111,-20l-100 -33z"/>
|
||||||
|
<path class="fil6" d="M61 900l330 -283c-9,-31 -23,-59 -40,-85 -20,-30 -45,-56 -75,-79 -30,-23 -69,-42 -116,-58l-99 -33 0 538z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After (image error) Size: 5.5 KiB |