commit fe29b6136f0f6ecb78180e154b0da058d9c15222 Author: Sergei Erjemin (Сергей Еремин) Date: Mon Nov 2 23:25:16 2020 +0300 Все работает (даже на хостинге) diff --git a/README.md b/README.md new file mode 100644 index 0000000..1adbf2a --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Проект DicQuo (Цитаты и Высказвания) + +Микросайт ротации цитат и высказываний. В будущем, возможно, со своим REST-API. + +[Инструкция по развертыванию на хостинге DreamHoast.com](deploy_to_dreamhost.md) \ No newline at end of file diff --git a/deploy_to_dreamhost.md b/deploy_to_dreamhost.md new file mode 100644 index 0000000..58c0095 --- /dev/null +++ b/deploy_to_dreamhost.md @@ -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//opt/python-3.8.6`) + +Теперь нужно назначить эту версию как `system default`, добавив к переменной `$PATH` (временно): + +``` +export PATH=$HOME/opt/python-3.8.6/bin:$PATH +``` + +------------------------------ +_Также можно добавить эту строку в файл `.bashrc` и/или `.bash_profile` в домашней директории `/home/.` Это нужно, чтобы сделать так, чтобы этот 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__ + diff --git a/dicquo/db.sqlite3 b/dicquo/db.sqlite3 new file mode 100644 index 0000000..5f43c52 Binary files /dev/null and b/dicquo/db.sqlite3 differ diff --git a/dicquo/dicquo/__init__.py b/dicquo/dicquo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dicquo/dicquo/asgi.py b/dicquo/dicquo/asgi.py new file mode 100644 index 0000000..dc016ba --- /dev/null +++ b/dicquo/dicquo/asgi.py @@ -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() diff --git a/dicquo/dicquo/my_secret.py b/dicquo/dicquo/my_secret.py new file mode 100644 index 0000000..ef2b3db --- /dev/null +++ b/dicquo/dicquo/my_secret.py @@ -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/' + diff --git a/dicquo/dicquo/settings.py b/dicquo/dicquo/settings.py new file mode 100644 index 0000000..ddef88d --- /dev/null +++ b/dicquo/dicquo/settings.py @@ -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', + } +} \ No newline at end of file diff --git a/dicquo/dicquo/urls.py b/dicquo/dicquo/urls.py new file mode 100644 index 0000000..db7ca02 --- /dev/null +++ b/dicquo/dicquo/urls.py @@ -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\d{1,12})_\S*$', views.by_id), +] + +urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/dicquo/dicquo/wsgi.py b/dicquo/dicquo/wsgi.py new file mode 100644 index 0000000..4e66643 --- /dev/null +++ b/dicquo/dicquo/wsgi.py @@ -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() diff --git a/dicquo/manage.py b/dicquo/manage.py new file mode 100644 index 0000000..acb8fd3 --- /dev/null +++ b/dicquo/manage.py @@ -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() diff --git a/dicquo/requarement.txt b/dicquo/requarement.txt new file mode 100644 index 0000000..b84f7ff --- /dev/null +++ b/dicquo/requarement.txt @@ -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 \ No newline at end of file diff --git a/dicquo/requarement_dev_home.txt b/dicquo/requarement_dev_home.txt new file mode 100644 index 0000000..d8e07e5 --- /dev/null +++ b/dicquo/requarement_dev_home.txt @@ -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 diff --git a/dicquo/templates/base.html b/dicquo/templates/base.html new file mode 100644 index 0000000..cfab720 --- /dev/null +++ b/dicquo/templates/base.html @@ -0,0 +1,33 @@ + +{% load static %} + + + + + + + + + + + + + + + + {% block Title %}{% endblock %} + + + + + + + 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 %} + + \ No newline at end of file diff --git a/dicquo/templates/blocks/cookie_warning.html b/dicquo/templates/blocks/cookie_warning.html new file mode 100644 index 0000000..b10ae8b --- /dev/null +++ b/dicquo/templates/blocks/cookie_warning.html @@ -0,0 +1,11 @@ + +
+ Тут используют cookie и ведут сбор технических данных о посещениях, потому как без этого интернет-сайты вообще почти не работают… + +
+ \ No newline at end of file diff --git a/dicquo/templates/blocks/header_nav.html b/dicquo/templates/blocks/header_nav.html new file mode 100644 index 0000000..42e3a59 --- /dev/null +++ b/dicquo/templates/blocks/header_nav.html @@ -0,0 +1,16 @@ +{% load static %} +
+ + + + +
+ + + + Блог | Добавить высказывание | + +
+ \ No newline at end of file diff --git a/dicquo/templates/blocks/tecnical_info.html b/dicquo/templates/blocks/tecnical_info.html new file mode 100644 index 0000000..84a10df --- /dev/null +++ b/dicquo/templates/blocks/tecnical_info.html @@ -0,0 +1,6 @@ + +
+  🕗 {{ ticks|stringformat:".6f" }} s ({% now 'c' %}) +
+ \ No newline at end of file diff --git a/dicquo/templates/index.html b/dicquo/templates/index.html new file mode 100644 index 0000000..cab4c61 --- /dev/null +++ b/dicquo/templates/index.html @@ -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" %} +
+ + {% if IMAGE %} + +
+
{{ DQ.szIntroHTML|safe }}
+
{{ DQ.szContentHTML|safe }}
+
{{ AUTHOR.szAuthorHTML|safe }}
+
+
+
{{ AUTHOR.szAuthor }}
+
+
{% else %}
{% endif %} +
+ {% for i in TAGS %}{{ i.name|safe }} {% endfor %} + +
+
+ + + + + +{% if not cookie_accept %}{% include "blocks/cookie_warning.html" %}{% endif %} +{% endblock %} diff --git a/dicquo/web/__init__.py b/dicquo/web/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dicquo/web/admin.py b/dicquo/web/admin.py new file mode 100644 index 0000000..01e23f6 --- /dev/null +++ b/dicquo/web/admin.py @@ -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"-empty-" + 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"-empty-" + + +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"-empty-" + + 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"-empty-" + + 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) diff --git a/dicquo/web/apps.py b/dicquo/web/apps.py new file mode 100644 index 0000000..7ff764c --- /dev/null +++ b/dicquo/web/apps.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +from django.apps import AppConfig + + +class WebConfig(AppConfig): + name = 'web' diff --git a/dicquo/web/migrations/0001_initial.py b/dicquo/web/migrations/0001_initial.py new file mode 100644 index 0000000..fd38f6a --- /dev/null +++ b/dicquo/web/migrations/0001_initial.py @@ -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='Автор и, если необходимо, краткая справка
Свертано в HTML по правилам типографики (рекламные URL вставляются тут)', 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='Теги через запятую… Регистр не чувствителен… Теги нужны для подстановки картинок и навигации', 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='Теги через запятую… Регистр не чувствителен… Теги нужны для подстановки картинок и навигации', 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='Автор и, если необходимо, краткая справка
Вступление перед цитатой, в HTML по правилам типографики', verbose_name='Вступление HTML')), + ('szContent', models.TextField(default='', help_text='Не обязательно. Вступление перед цитатой.', max_length=256, verbose_name='Высказывание')), + ('szContentHTML', models.TextField(default='', help_text='Высказывание Крылатое -- крылатое, пародоксальное и все такое', verbose_name='Высказывание HTML')), + ('imFileOG', models.ImageField(default='', help_text='Картинка для социальной сети (будет создана автоматически).
Файл с картинкой (png).', max_length=136, upload_to='img2og', verbose_name='OG-image
')), + ('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='Автор изречения или цитаты (не обязательно, но желательно)', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='web.tbauthor', verbose_name='Автор')), + ('kImages', models.ForeignKey(default=None, help_text='Ссылка на картинку, в табличке картинок (не обязательно)
если нужна именно данная картинка, а не выбранная автоматически', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='web.tbimages', verbose_name='Картинка')), + ('kOrigin', models.ForeignKey(default=None, help_text='Откуда взята циатата, высказывание, изречение (не обязательно, но желательно)', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='web.tborigin', verbose_name='Источник')), + ('tags', taggit.managers.TaggableManager(help_text='Теги через запятую… Регистр не чувствителен… Теги нужны для подстановки картинок и навигации', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Теги')), + ], + options={ + 'verbose_name': 'ВЫСКАЗЫВАНИЕ', + 'verbose_name_plural': 'ВЫСКАЗЫВАНИЯ', + 'ordering': ['id'], + }, + ), + ] diff --git a/dicquo/web/migrations/__init__.py b/dicquo/web/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dicquo/web/models.py b/dicquo/web/models.py new file mode 100644 index 0000000..a437a41 --- /dev/null +++ b/dicquo/web/models.py @@ -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"Теги через запятую… Регистр не чувствителен… Теги нужны для подстановки картинок и навигации" + ) + 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"Автор и, если необходимо, краткая справка
" + u"Свертано в HTML по правилам типографики (рекламные URL вставляются тут)" + ) + 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"Теги через запятую… Регистр не чувствителен… Теги нужны для подстановки картинок и навигации" + ) + 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"Автор и, если необходимо, краткая справка
" + u"Вступление перед цитатой, в HTML по правилам типографики
" + ) + 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"Высказывание Крылатое -- крылатое, пародоксальное и все такое" + ) + kAuthor = models.ForeignKey( + TbAuthor, + default=None, + blank=True, + on_delete=models.DO_NOTHING, + verbose_name=u"Автор", + help_text=u"Автор изречения или цитаты (не обязательно, но желательно)" + ) + kOrigin = models.ForeignKey( + TbOrigin, + default=None, + blank=True, + on_delete=models.DO_NOTHING, + verbose_name=u"Источник", + help_text=u"Откуда взята циатата, высказывание, изречение (не обязательно, но желательно)" + ) + kImages = models.ForeignKey( + TbImages, + default=None, + blank=True, + on_delete=models.DO_NOTHING, + verbose_name=u"Картинка", + help_text=u"Ссылка на картинку, в табличке картинок (не обязательно)
" + u"если нужна именно данная картинка, а не выбранная автоматически" + ) + imFileOG = models.ImageField( + max_length=136, + upload_to="img2og", + default=u"", + blank=True, + verbose_name=u"OG-image", + help_text=u"Картинка для социальной сети (будет создана автоматически).
" + u"Файл с картинкой (png)." + ) + 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"Теги через запятую… Регистр не чувствителен… Теги нужны для подстановки картинок и навигации" + ) + 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', ] diff --git a/dicquo/web/tests.py b/dicquo/web/tests.py new file mode 100644 index 0000000..3c38043 --- /dev/null +++ b/dicquo/web/tests.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +from django.test import TestCase + +# Create your tests here. diff --git a/dicquo/web/views.py b/dicquo/web/views.py new file mode 100644 index 0000000..5ba0833 --- /dev/null +++ b/dicquo/web/views.py @@ -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 diff --git a/passenger_wsgi.py b/passenger_wsgi.py new file mode 100644 index 0000000..95c3f13 --- /dev/null +++ b/passenger_wsgi.py @@ -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() diff --git a/public/favicon.gif b/public/favicon.gif new file mode 100644 index 0000000..8785be7 Binary files /dev/null and b/public/favicon.gif differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..e83ec26 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..49cc2e5 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/media/img2/boris-grebenschikov-02.jpg b/public/media/img2/boris-grebenschikov-02.jpg new file mode 100644 index 0000000..b5d5cd7 Binary files /dev/null and b/public/media/img2/boris-grebenschikov-02.jpg differ diff --git a/public/media/img2/boris-grebenschikov.png b/public/media/img2/boris-grebenschikov.png new file mode 100644 index 0000000..6d87af4 Binary files /dev/null and b/public/media/img2/boris-grebenschikov.png differ diff --git a/public/media/img2/dzhonni-kesh-1954.jpg b/public/media/img2/dzhonni-kesh-1954.jpg new file mode 100644 index 0000000..0eac29d Binary files /dev/null and b/public/media/img2/dzhonni-kesh-1954.jpg differ diff --git a/public/media/img2/jim-morrison-01.jpg b/public/media/img2/jim-morrison-01.jpg new file mode 100644 index 0000000..ae7b156 Binary files /dev/null and b/public/media/img2/jim-morrison-01.jpg differ diff --git a/public/media/img2/leonard-cohen-01.jpg b/public/media/img2/leonard-cohen-01.jpg new file mode 100644 index 0000000..d560441 Binary files /dev/null and b/public/media/img2/leonard-cohen-01.jpg differ diff --git a/public/media/img2/leonard-cohen-02.jpg b/public/media/img2/leonard-cohen-02.jpg new file mode 100644 index 0000000..4e372f4 Binary files /dev/null and b/public/media/img2/leonard-cohen-02.jpg differ diff --git a/public/media/img2/lou-reed-001.jpg b/public/media/img2/lou-reed-001.jpg new file mode 100644 index 0000000..6bcfbdb Binary files /dev/null and b/public/media/img2/lou-reed-001.jpg differ diff --git a/public/media/img2/nick-cave-01.jpg b/public/media/img2/nick-cave-01.jpg new file mode 100644 index 0000000..b7d076d Binary files /dev/null and b/public/media/img2/nick-cave-01.jpg differ diff --git a/public/media/img2/nick-cave-02.Cave__.2020_.png b/public/media/img2/nick-cave-02.Cave__.2020_.png new file mode 100644 index 0000000..875ab03 Binary files /dev/null and b/public/media/img2/nick-cave-02.Cave__.2020_.png differ diff --git a/public/media/img2/sergej-kuryohin-01.jpg b/public/media/img2/sergej-kuryohin-01.jpg new file mode 100644 index 0000000..ad9b2fd Binary files /dev/null and b/public/media/img2/sergej-kuryohin-01.jpg differ diff --git a/public/media/img2/sergej-kuryohin-02.jpg b/public/media/img2/sergej-kuryohin-02.jpg new file mode 100644 index 0000000..b20d77a Binary files /dev/null and b/public/media/img2/sergej-kuryohin-02.jpg differ diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..37b5f3b --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,7 @@ +# DicQuo +User-Agent: * +Allow: / +Disallow: +Host: dq.cube2.ru +Sitemap: https://dq.cube2.ru/sitemap.xml + diff --git a/public/static/css/dicquo.css b/public/static/css/dicquo.css new file mode 100644 index 0000000..b128054 --- /dev/null +++ b/public/static/css/dicquo.css @@ -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; } diff --git a/public/static/img/cubex.png b/public/static/img/cubex.png new file mode 100644 index 0000000..22b0afa Binary files /dev/null and b/public/static/img/cubex.png differ diff --git a/public/static/img/favicon.gif b/public/static/img/favicon.gif new file mode 100644 index 0000000..8785be7 Binary files /dev/null and b/public/static/img/favicon.gif differ diff --git a/public/static/img/favicon.ico b/public/static/img/favicon.ico new file mode 100644 index 0000000..e83ec26 Binary files /dev/null and b/public/static/img/favicon.ico differ diff --git a/public/static/img/favicon.png b/public/static/img/favicon.png new file mode 100644 index 0000000..1df34c4 Binary files /dev/null and b/public/static/img/favicon.png differ diff --git a/public/static/img/greyzz.png b/public/static/img/greyzz.png new file mode 100644 index 0000000..ffac996 Binary files /dev/null and b/public/static/img/greyzz.png differ diff --git a/public/static/svgs/dq-logo.svg b/public/static/svgs/dq-logo.svg new file mode 100644 index 0000000..49cc2e5 --- /dev/null +++ b/public/static/svgs/dq-logo.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +