parent
e5f14dee47
commit
fe060b0547
@ -0,0 +1,18 @@
|
|||||||
|
CONTAINERS_NAME=rsrv
|
||||||
|
# The name of the image to use for the containers
|
||||||
|
DJANGO_PORT=8000
|
||||||
|
DEBUG=False
|
||||||
|
# The django container settings and enviroment
|
||||||
|
POSTGRES_DB=rsrv_db
|
||||||
|
POSTGRES_HOST=db
|
||||||
|
POSTGRES_PORT=5435
|
||||||
|
POSTGRES_USER=rsrv_user
|
||||||
|
POSTGRES_PASSWORD=rsrv_pass
|
||||||
|
POSTGRES_VOLUME_PATH=./pg_data
|
||||||
|
POSTGRES_HOST_AUTH_METHOD=trust
|
||||||
|
POSTGRES_VOLUME_BACKUP_PATH=./pg_dumps
|
||||||
|
# The database container settings and enviroment
|
||||||
|
MARTIN_PORT=3000
|
||||||
|
# The martin container settings and enviroment
|
||||||
|
DOMAIN=social.ru.com
|
||||||
|
# The frontend and nginx container settings and enviroment
|
||||||
@ -0,0 +1,136 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# celery beat schedule file
|
||||||
|
celerybeat-schedule
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
.idea/
|
||||||
|
cells1/
|
||||||
|
cells_final/
|
||||||
|
cells_final1/
|
||||||
|
schools_final.xlsx
|
||||||
|
certs/
|
||||||
|
pg_data/
|
||||||
|
.env.dev
|
||||||
|
.env.prod
|
||||||
|
.env.prod_busines
|
||||||
|
|
||||||
|
static/
|
||||||
|
media/
|
||||||
|
data/
|
||||||
|
nginx/nginx.conf
|
||||||
|
nginx/nginx.dev.conf
|
||||||
|
nginx/nginx.conf.prod_business
|
||||||
|
nginx/nginx.conf.prod
|
||||||
|
docker-compose.dev.yml
|
||||||
|
pg_dumps/
|
||||||
|
django_static/
|
||||||
|
django_media/
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
FROM python:3.8
|
||||||
|
|
||||||
|
RUN apt-get update &&\
|
||||||
|
apt-get install -y binutils libproj-dev gdal-bin
|
||||||
|
|
||||||
|
ENV PYTHONUNBUFFERED 1
|
||||||
|
|
||||||
|
RUN mkdir /code
|
||||||
|
|
||||||
|
WORKDIR /code
|
||||||
|
|
||||||
|
ADD requirements.txt /code/
|
||||||
|
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
ADD . /code/
|
||||||
@ -0,0 +1,87 @@
|
|||||||
|
version: '3.5'
|
||||||
|
|
||||||
|
x-postgres-variables: &postgres-variables
|
||||||
|
POSTGRES_DB: "${POSTGRES_DB}"
|
||||||
|
POSTGRES_HOST: "${POSTGRES_HOST}"
|
||||||
|
POSTGRES_PORT: "${POSTGRES_PORT}"
|
||||||
|
POSTGRES_USER: "${POSTGRES_USER}"
|
||||||
|
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
||||||
|
POSTGRES_HOST_AUTH_METHOD: "${POSTGRES_HOST_AUTH_METHOD}"
|
||||||
|
|
||||||
|
x-django-variables: &django-variables
|
||||||
|
PYTHONUNBUFFERED: 1
|
||||||
|
DJANGO_PORT: "${DJANGO_PORT}"
|
||||||
|
DEBUG: "${DEBUG}"
|
||||||
|
|
||||||
|
x-frontend-variables: &frontend-variables
|
||||||
|
DOMAIN: "${DOMAIN}"
|
||||||
|
REACT_APP_DOMAIN_URL: "https://${DOMAIN}/"
|
||||||
|
|
||||||
|
|
||||||
|
x-martin-variables: &martin-variables
|
||||||
|
MARTIN_PORT: "${MARTIN_PORT}"
|
||||||
|
|
||||||
|
|
||||||
|
services:
|
||||||
|
django:
|
||||||
|
container_name: ${CONTAINERS_NAME}_django
|
||||||
|
build: .
|
||||||
|
command: >
|
||||||
|
sh -c "python manage.py migrate &&
|
||||||
|
python manage.py collectstatic --noinput &&
|
||||||
|
python manage.py runserver 0.0.0.0:${DJANGO_PORT}"
|
||||||
|
environment:
|
||||||
|
<<: *postgres-variables
|
||||||
|
<<: *django-variables
|
||||||
|
volumes:
|
||||||
|
- .:/code
|
||||||
|
- ./django_static/:/code/django_static/
|
||||||
|
- ./media/:/code/media/
|
||||||
|
ports:
|
||||||
|
- "${DJANGO_PORT}:${DJANGO_PORT}"
|
||||||
|
expose:
|
||||||
|
- "${DJANGO_PORT}"
|
||||||
|
restart: always
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
|
||||||
|
db:
|
||||||
|
container_name: ${CONTAINERS_NAME}_db
|
||||||
|
image: mdillon/postgis
|
||||||
|
environment:
|
||||||
|
<<: *postgres-variables
|
||||||
|
ports:
|
||||||
|
- "${POSTGRES_PORT}:${POSTGRES_PORT}"
|
||||||
|
expose:
|
||||||
|
- "${POSTGRES_PORT}"
|
||||||
|
volumes:
|
||||||
|
- ${POSTGRES_VOLUME_PATH}:/var/lib/postgresql/data
|
||||||
|
command: -p ${POSTGRES_PORT}
|
||||||
|
|
||||||
|
martin:
|
||||||
|
container_name: ${CONTAINERS_NAME}_martin
|
||||||
|
image: urbica/martin
|
||||||
|
ports:
|
||||||
|
- "${MARTIN_PORT}:3000"
|
||||||
|
environment:
|
||||||
|
<<: *martin-variables
|
||||||
|
WATCH_MODE: "true"
|
||||||
|
DATABASE_URL: "postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:${POSTGRES_PORT}/${POSTGRES_DB}"
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
- django
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
container_name: ${CONTAINERS_NAME}_frontend
|
||||||
|
build: dit_frontend
|
||||||
|
volumes:
|
||||||
|
- ./build/:/usr/src/dit_frontend/build/
|
||||||
|
command:
|
||||||
|
sh -c "yarn build"
|
||||||
|
environment:
|
||||||
|
<<: *frontend-variables
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
- martin
|
||||||
|
- django
|
||||||
@ -0,0 +1,120 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"model": "service.config",
|
||||||
|
"pk": 1,
|
||||||
|
"fields": {
|
||||||
|
"name": "Point",
|
||||||
|
"data": {
|
||||||
|
"id": {
|
||||||
|
"label": "ID",
|
||||||
|
"legend": "ID",
|
||||||
|
"help": "ID"
|
||||||
|
},
|
||||||
|
"point": {
|
||||||
|
"label": "Геометрия",
|
||||||
|
"legend": "Геометрия",
|
||||||
|
"help": "Геометрия"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"label": "Название",
|
||||||
|
"legend": "Название",
|
||||||
|
"help": "Название"
|
||||||
|
},
|
||||||
|
"adress": {
|
||||||
|
"label": "Адрес",
|
||||||
|
"legend": "Адрес",
|
||||||
|
"help": "Адрес"
|
||||||
|
},
|
||||||
|
"district": {
|
||||||
|
"label": "Район",
|
||||||
|
"legend": "Район",
|
||||||
|
"help": "Район"
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"label": "link",
|
||||||
|
"legend": "link",
|
||||||
|
"help": "link"
|
||||||
|
},
|
||||||
|
"area": {
|
||||||
|
"label": "Площадь",
|
||||||
|
"legend": "Площадь",
|
||||||
|
"help": "Площадь"
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"label": "Цена",
|
||||||
|
"legend": "Цена",
|
||||||
|
"help": "Цена"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "service.config",
|
||||||
|
"pk": 2,
|
||||||
|
"fields": {
|
||||||
|
"name": "Polygon",
|
||||||
|
"data": {
|
||||||
|
"id": {
|
||||||
|
"label": "ID",
|
||||||
|
"legend": "ID",
|
||||||
|
"help": "ID"
|
||||||
|
},
|
||||||
|
"geometry": {
|
||||||
|
"label": "Геометрия",
|
||||||
|
"legend": "Геометрия",
|
||||||
|
"help": "Геометрия"
|
||||||
|
},
|
||||||
|
"cellid": {
|
||||||
|
"label": "ID клетки",
|
||||||
|
"legend": "ID клетки",
|
||||||
|
"help": "ID клетки"
|
||||||
|
},
|
||||||
|
"home": {
|
||||||
|
"label": "Дом",
|
||||||
|
"legend": "Дом",
|
||||||
|
"help": "Дом"
|
||||||
|
},
|
||||||
|
"job": {
|
||||||
|
"label": "Работа",
|
||||||
|
"legend": "Работа",
|
||||||
|
"help": "Работа"
|
||||||
|
},
|
||||||
|
"horeca": {
|
||||||
|
"label": "Рестораны",
|
||||||
|
"legend": "Рестораны",
|
||||||
|
"help": "Рестораны"
|
||||||
|
},
|
||||||
|
"brand": {
|
||||||
|
"label": "Бренды",
|
||||||
|
"legend": "Бренды",
|
||||||
|
"help": "Бренды"
|
||||||
|
},
|
||||||
|
"univer": {
|
||||||
|
"label": "Университеты",
|
||||||
|
"legend": "Университеты",
|
||||||
|
"help": "Университеты"
|
||||||
|
},
|
||||||
|
"hotel": {
|
||||||
|
"label": "Отели",
|
||||||
|
"legend": "Отели",
|
||||||
|
"help": "Отели"
|
||||||
|
},
|
||||||
|
"TC": {
|
||||||
|
"label": "ТЦ",
|
||||||
|
"legend": "ТЦ",
|
||||||
|
"help": "ТЦ"
|
||||||
|
},
|
||||||
|
"BC": {
|
||||||
|
"label": "БЦ",
|
||||||
|
"legend": "БЦ",
|
||||||
|
"help": "БЦ"
|
||||||
|
},
|
||||||
|
"metro": {
|
||||||
|
"label": "Метро",
|
||||||
|
"legend": "Метро",
|
||||||
|
"help": "Метро"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'postamates.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()
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'postamates.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
@ -0,0 +1,143 @@
|
|||||||
|
"""
|
||||||
|
Django settings for HACK2 project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 3.2.8.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.2/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/3.2/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 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.2/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = 'django-insecure-5czma@e7b(e4v+c*@bkknj(*em%@x52jizednhy6lye)_@ox4@'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = os.getenv('DEBUG', False) == 'True'
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'corsheaders',
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
'service.apps.ServiceConfig',
|
||||||
|
'rest_framework',
|
||||||
|
'django_json_widget',
|
||||||
|
'django.contrib.gis',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'corsheaders.middleware.CorsMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'postamates.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 = 'postamates.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.contrib.gis.db.backends.postgis',
|
||||||
|
'NAME': os.getenv('POSTGRES_DB', 'postgres'),
|
||||||
|
'USER': os.getenv('POSTGRES_USER', 'postgres'),
|
||||||
|
'PASSWORD': os.getenv('POSTGRES_PASSWORD', 'postgres'),
|
||||||
|
'HOST': os.getenv('POSTGRES_HOST', 'localhost'),
|
||||||
|
'PORT': os.getenv('POSTGRES_PORT', 5432),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/3.2/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.2/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'ru'
|
||||||
|
|
||||||
|
TIME_ZONE = 'Europe/Moscow'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_L10N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/3.2/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = '/django_static/'
|
||||||
|
STATIC_ROOT = os.path.join(BASE_DIR, 'django_static')
|
||||||
|
MEDIA_URL = '/django_media/'
|
||||||
|
MEDIA_ROOT = os.path.join(BASE_DIR, 'django_media')
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
|
|
||||||
|
CORS_ORIGIN_ALLOW_ALL = True # If this is used then `CORS_ORIGIN_WHITELIST` will not have any effect
|
||||||
|
CORS_ALLOW_CREDENTIALS = True
|
||||||
|
CORS_ORIGIN_ALLOW = True
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
from django.conf.urls.static import static
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
path('api/', include('service.urls')),
|
||||||
|
]
|
||||||
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'postamates.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
Django==3.2
|
||||||
|
djangorestframework==3.11.1
|
||||||
|
psycopg2-binary==2.9.3
|
||||||
|
pandas==1.4.2
|
||||||
|
xlrd==1.2.0
|
||||||
|
tqdm==4.64.0
|
||||||
|
gunicorn==19.9.0
|
||||||
|
django-cors-headers==3.12.0
|
||||||
|
pyshp==2.3.0
|
||||||
|
matplotlib==3.5.2
|
||||||
|
openpyxl==3.0.10
|
||||||
|
django-json-widget
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from service.models import Polygon, Point, Config
|
||||||
|
from django.db import models
|
||||||
|
from django_json_widget.widgets import JSONEditorWidget
|
||||||
|
|
||||||
|
# admin.site.register(Polygon)
|
||||||
|
# admin.site.register(Point)
|
||||||
|
@admin.register(Config)
|
||||||
|
class YourModelAdmin(admin.ModelAdmin):
|
||||||
|
formfield_overrides = {
|
||||||
|
models.JSONField: {'widget': JSONEditorWidget},
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'service'
|
||||||
@ -0,0 +1 @@
|
|||||||
|
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
import json
|
||||||
|
from service import models
|
||||||
|
|
||||||
|
|
||||||
|
def create_fixture(name, file_path, merge):
|
||||||
|
if merge:
|
||||||
|
with open(file_path, 'r') as f:
|
||||||
|
old_data = json.load(f)
|
||||||
|
max_pk = max([x['pk'] for x in old_data])
|
||||||
|
else:
|
||||||
|
max_pk = 0
|
||||||
|
model = getattr(models, name)
|
||||||
|
model_default_data = {
|
||||||
|
f.name: {
|
||||||
|
"label": f.verbose_name,
|
||||||
|
"legend": f.verbose_name,
|
||||||
|
"help": f.verbose_name
|
||||||
|
}
|
||||||
|
for f in model._meta.fields
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"model": "service.config",
|
||||||
|
"pk": max_pk + 1,
|
||||||
|
"fields": {
|
||||||
|
"name": f"{name}",
|
||||||
|
"data": model_default_data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
with open(file_path, 'w') as f:
|
||||||
|
if merge:
|
||||||
|
old_data.append(data)
|
||||||
|
json.dump(old_data, f, ensure_ascii=False, indent=4)
|
||||||
|
else:
|
||||||
|
json.dump([data], f, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
requires_system_checks = False
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('-n', '--name', type=str)
|
||||||
|
parser.add_argument('-f', '--file', type=str)
|
||||||
|
parser.add_argument('-m', '--merge', type=bool, default=False)
|
||||||
|
|
||||||
|
def handle(self, name, file, merge, *args, **options):
|
||||||
|
create_fixture(name, file, merge)
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from service.models import Point
|
||||||
|
from django.contrib.gis.geos import Point as GeoPoint
|
||||||
|
from pandas import read_excel
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
|
||||||
|
def import_points(file_path):
|
||||||
|
Point.objects.all().delete()
|
||||||
|
point_models = []
|
||||||
|
df = read_excel(file_path)
|
||||||
|
df = df.fillna(0)
|
||||||
|
df_rows = list(df.iterrows())
|
||||||
|
for id_, i in tqdm(df_rows):
|
||||||
|
data = i.to_dict()
|
||||||
|
lat, lng = data.pop('lat'),data.pop('lng')
|
||||||
|
geometry = GeoPoint(lng, lat, srid=4326)
|
||||||
|
try:
|
||||||
|
point_models.append(Point(point=geometry, **data))
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
pass
|
||||||
|
Point.objects.bulk_create(point_models, batch_size=10000)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
requires_system_checks = False
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('-f', '--file', type=str)
|
||||||
|
|
||||||
|
def handle(self, file, *args, **options):
|
||||||
|
import_points(file)
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from service.models import Polygon
|
||||||
|
import shapefile
|
||||||
|
from django.contrib.gis.geos import Polygon as GeoPolygon
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
|
||||||
|
def import_poly(file_path):
|
||||||
|
Polygon.objects.all().delete()
|
||||||
|
poly_models = []
|
||||||
|
shape = shapefile.Reader(file_path)
|
||||||
|
shape_records = list(shape.shapeRecords())
|
||||||
|
for i in tqdm(shape_records):
|
||||||
|
polygon = i.shape.__geo_interface__
|
||||||
|
data = i.__geo_interface__['properties']
|
||||||
|
geom = GeoPolygon(polygon['coordinates'][0], srid=4326)
|
||||||
|
poly_models.append(Polygon(geometry=geom, **data))
|
||||||
|
Polygon.objects.bulk_create(poly_models, batch_size=10000)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
requires_system_checks = False
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument('-f', '--file', type=str)
|
||||||
|
|
||||||
|
def handle(self, file, *args, **options):
|
||||||
|
import_poly(file)
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
# Generated by Django 3.2 on 2022-07-09 16:01
|
||||||
|
|
||||||
|
import django.contrib.gis.db.models.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Point',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('point', django.contrib.gis.db.models.fields.PointField(srid=4326)),
|
||||||
|
('name', models.CharField(blank=True, max_length=256, null=True)),
|
||||||
|
('adress', models.CharField(blank=True, max_length=512, null=True)),
|
||||||
|
('district', models.CharField(blank=True, max_length=512, null=True)),
|
||||||
|
('area', models.FloatField()),
|
||||||
|
('price', models.IntegerField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Poly',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('geometry', django.contrib.gis.db.models.fields.PolygonField(srid=4326)),
|
||||||
|
('cellid', models.IntegerField()),
|
||||||
|
('home', models.IntegerField()),
|
||||||
|
('job', models.IntegerField()),
|
||||||
|
('horeca', models.IntegerField()),
|
||||||
|
('brand', models.IntegerField()),
|
||||||
|
('univer', models.IntegerField()),
|
||||||
|
('hotel', models.IntegerField()),
|
||||||
|
('TC', models.IntegerField()),
|
||||||
|
('BC', models.IntegerField()),
|
||||||
|
('metro', models.FloatField()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2 on 2022-07-09 16:05
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('service', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='point',
|
||||||
|
name='link',
|
||||||
|
field=models.URLField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.2 on 2022-07-09 16:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('service', '0002_point_link'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='point',
|
||||||
|
name='price',
|
||||||
|
field=models.BigIntegerField(),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,64 @@
|
|||||||
|
# Generated by Django 3.2 on 2022-07-09 17:04
|
||||||
|
|
||||||
|
import django.contrib.gis.db.models.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('service', '0003_alter_point_price'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Polygon',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('geometry', django.contrib.gis.db.models.fields.PolygonField(srid=4326, verbose_name='Геометрия')),
|
||||||
|
('cellid', models.IntegerField(verbose_name='ID клетки')),
|
||||||
|
('home', models.IntegerField(verbose_name='Дом')),
|
||||||
|
('job', models.IntegerField(verbose_name='Работа')),
|
||||||
|
('horeca', models.IntegerField(verbose_name='Рестораны')),
|
||||||
|
('brand', models.IntegerField(verbose_name='Бренды')),
|
||||||
|
('univer', models.IntegerField(verbose_name='Университеты')),
|
||||||
|
('hotel', models.IntegerField(verbose_name='Отели')),
|
||||||
|
('TC', models.IntegerField(verbose_name='ТЦ')),
|
||||||
|
('BC', models.IntegerField(verbose_name='БЦ')),
|
||||||
|
('metro', models.FloatField(verbose_name='Метро')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='Poly',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='point',
|
||||||
|
name='adress',
|
||||||
|
field=models.CharField(blank=True, max_length=512, null=True, verbose_name='Адрес'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='point',
|
||||||
|
name='area',
|
||||||
|
field=models.FloatField(verbose_name='Площадь'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='point',
|
||||||
|
name='district',
|
||||||
|
field=models.CharField(blank=True, max_length=512, null=True, verbose_name='Район'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='point',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(blank=True, max_length=256, null=True, verbose_name='Название'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='point',
|
||||||
|
name='point',
|
||||||
|
field=django.contrib.gis.db.models.fields.PointField(srid=4326, verbose_name='Геометрия'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='point',
|
||||||
|
name='price',
|
||||||
|
field=models.BigIntegerField(verbose_name='Цена'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 3.2 on 2022-07-11 09:03
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('service', '0004_auto_20220709_2004'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Config',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=256, unique=True, verbose_name='Название модели')),
|
||||||
|
('data', models.JSONField(verbose_name='Данные')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Конфигурация',
|
||||||
|
'verbose_name_plural': 'Конфигурация',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.contrib.gis.db import models as gis_models
|
||||||
|
|
||||||
|
|
||||||
|
class Polygon(models.Model):
|
||||||
|
geometry = gis_models.PolygonField(verbose_name='Геометрия')
|
||||||
|
cellid = models.IntegerField(verbose_name='ID клетки')
|
||||||
|
home = models.IntegerField(verbose_name='Дом')
|
||||||
|
job = models.IntegerField(verbose_name='Работа')
|
||||||
|
horeca = models.IntegerField(verbose_name='Рестораны')
|
||||||
|
brand = models.IntegerField(verbose_name='Бренды')
|
||||||
|
univer = models.IntegerField(verbose_name='Университеты')
|
||||||
|
hotel = models.IntegerField(verbose_name='Отели')
|
||||||
|
TC = models.IntegerField(verbose_name='ТЦ')
|
||||||
|
BC = models.IntegerField(verbose_name='БЦ')
|
||||||
|
metro = models.FloatField(verbose_name='Метро')
|
||||||
|
|
||||||
|
|
||||||
|
class Point(models.Model):
|
||||||
|
point = gis_models.PointField(verbose_name='Геометрия')
|
||||||
|
name = models.CharField(max_length=256, blank=True, null=True, verbose_name='Название')
|
||||||
|
adress = models.CharField(max_length=512, blank=True, null=True, verbose_name='Адрес')
|
||||||
|
district = models.CharField(max_length=512, blank=True, null=True, verbose_name='Район')
|
||||||
|
link = models.URLField(blank=True, null=True)
|
||||||
|
area = models.FloatField(verbose_name='Площадь')
|
||||||
|
price = models.BigIntegerField(verbose_name='Цена')
|
||||||
|
|
||||||
|
|
||||||
|
class Config(models.Model):
|
||||||
|
name = models.CharField(max_length=256, unique=True, verbose_name='Название модели')
|
||||||
|
data = models.JSONField(verbose_name='Данные')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'Конфигурация'
|
||||||
|
verbose_name_plural = 'Конфигурация'
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
from . import models
|
||||||
|
from service.utils import get_model_column_names
|
||||||
|
|
||||||
|
|
||||||
|
class PolygonSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Polygon
|
||||||
|
fields = get_model_column_names(model, [])
|
||||||
|
|
||||||
|
|
||||||
|
class PointSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.Point
|
||||||
|
fields = get_model_column_names(model, [])
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from django.conf.urls import url
|
||||||
|
from rest_framework import routers
|
||||||
|
from rest_framework.authtoken import views as rf_views
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
app_name = 'HACK2'
|
||||||
|
router = routers.DefaultRouter()
|
||||||
|
|
||||||
|
router.register('polygons', views.PolygonViewSet, basename='polygons')
|
||||||
|
router.register('points', views.PointViewSet, basename='points')
|
||||||
|
|
||||||
|
urlpatterns = router.urls
|
||||||
|
|
||||||
|
urlpatterns += [
|
||||||
|
]
|
||||||
@ -0,0 +1,2 @@
|
|||||||
|
def get_model_column_names(model, exclude_fields_names):
|
||||||
|
return [x.column for x in model._meta.get_fields() if str(x.column) not in exclude_fields_names]
|
||||||
@ -0,0 +1,130 @@
|
|||||||
|
import datetime
|
||||||
|
from service import serializers
|
||||||
|
from service import models
|
||||||
|
from rest_framework import viewsets
|
||||||
|
from rest_framework.permissions import AllowAny
|
||||||
|
from django.core.serializers import serialize
|
||||||
|
from rest_framework.response import Response
|
||||||
|
import json
|
||||||
|
from django.core.cache import cache
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from django.db.models import Max, Min
|
||||||
|
from django.conf import settings
|
||||||
|
from service.management.commands.create_poly import import_poly
|
||||||
|
from service.management.commands.create_points import import_points
|
||||||
|
from rest_framework.parsers import MultiPartParser
|
||||||
|
import os
|
||||||
|
from django.core.files.storage import default_storage
|
||||||
|
from django.core.files.base import ContentFile
|
||||||
|
from service.utils import get_model_column_names
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.contrib import messages
|
||||||
|
|
||||||
|
|
||||||
|
def get_min_max_filters(model, exclude_fields_names):
|
||||||
|
d = {}
|
||||||
|
l = []
|
||||||
|
fields = get_model_column_names(model, exclude_fields_names)
|
||||||
|
for i in fields:
|
||||||
|
l.append(Min(i))
|
||||||
|
l.append(Max(i))
|
||||||
|
qs = model.objects.aggregate(*l)
|
||||||
|
for i in fields:
|
||||||
|
d[i] = [qs[f'{i}__min'], qs[f'{i}__max']]
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def get_full_filters(model, exclude_fields_names):
|
||||||
|
d = {}
|
||||||
|
fields = get_model_column_names(model, exclude_fields_names)
|
||||||
|
values = model.objects.defer('pk').values(*fields)
|
||||||
|
for n, i in enumerate(fields):
|
||||||
|
d[i] = sorted([v[i] for v in values])
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def get_model_naming(model):
|
||||||
|
return models.Config.objects.get(name=f'{model.__name__}').data
|
||||||
|
|
||||||
|
|
||||||
|
class PolygonViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
serializer_class = serializers.PolygonSerializer
|
||||||
|
permission_classes = [AllowAny]
|
||||||
|
parser_classes = (MultiPartParser,)
|
||||||
|
|
||||||
|
@action(detail=False, methods=['get'])
|
||||||
|
def filters(self, request):
|
||||||
|
d = cache.get('polygon_filters')
|
||||||
|
if settings.DEBUG:
|
||||||
|
d = None
|
||||||
|
if d is None:
|
||||||
|
d = get_full_filters(models.Polygon, ['id', 'geometry'])
|
||||||
|
cache.set('polygon_filters', d, 60 * 60 * 24)
|
||||||
|
return Response(d, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@action(detail=False, methods=['get'])
|
||||||
|
def naming(self, request):
|
||||||
|
return Response(get_model_naming(models.Polygon), status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@action(detail=False, methods=['post'])
|
||||||
|
def file_import(self, request):
|
||||||
|
try:
|
||||||
|
file = request.FILES['file'].file
|
||||||
|
path = default_storage.save(f'cells/cells_{datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}.zip', ContentFile(file.read()))
|
||||||
|
import_poly(os.path.join(settings.MEDIA_ROOT, path))
|
||||||
|
cache.delete('polygon_filters')
|
||||||
|
messages.add_message(request, messages.INFO, 'Данные успешно импортированы')
|
||||||
|
except Exception as e:
|
||||||
|
messages.add_message(request, messages.ERROR, f'Ошибка импорта: {e}')
|
||||||
|
return HttpResponseRedirect('/admin/')
|
||||||
|
|
||||||
|
|
||||||
|
class PointViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
serializer_class = serializers.PointSerializer
|
||||||
|
permission_classes = [AllowAny]
|
||||||
|
queryset = models.Point.objects.all()
|
||||||
|
|
||||||
|
def list(self, request, *args, **kwargs):
|
||||||
|
queryset = self.get_queryset()
|
||||||
|
d = cache.get('points')
|
||||||
|
if settings.DEBUG:
|
||||||
|
d = None
|
||||||
|
if d is None:
|
||||||
|
d = json.loads(serialize('geojson', queryset,
|
||||||
|
geometry_field='point',
|
||||||
|
fields=get_model_column_names(models.Point,['point'])
|
||||||
|
))
|
||||||
|
cache.set('points', d, 60 * 60 * 24)
|
||||||
|
|
||||||
|
resp = Response(d)
|
||||||
|
resp["Access-Control-Allow-Origin"] = '*'
|
||||||
|
resp["Access-Control-Allow-Methods"] = 'GET,PUT, OPTIONS'
|
||||||
|
resp["Access-Control-Max-Age"] = '1000'
|
||||||
|
resp["Access-Control-Allow-Headers"] = 'X-Requested-With, Content-Type'
|
||||||
|
return resp
|
||||||
|
|
||||||
|
@action(detail=False, methods=['get'])
|
||||||
|
def filters(self, request):
|
||||||
|
resp = Response(get_min_max_filters(models.Point, ['id', 'point', 'is2025']))
|
||||||
|
resp["Access-Control-Allow-Origin"] = '*'
|
||||||
|
resp["Access-Control-Allow-Methods"] = 'GET,PUT, OPTIONS'
|
||||||
|
resp["Access-Control-Max-Age"] = '1000'
|
||||||
|
resp["Access-Control-Allow-Headers"] = 'X-Requested-With, Content-Type'
|
||||||
|
return resp
|
||||||
|
|
||||||
|
@action(detail=False, methods=['post'])
|
||||||
|
def file_import(self, request):
|
||||||
|
try:
|
||||||
|
file = request.FILES['file'].file
|
||||||
|
path = default_storage.save(f'points/points_{datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S")}.csv', ContentFile(file.read()))
|
||||||
|
import_points(os.path.join(settings.MEDIA_ROOT, path))
|
||||||
|
cache.delete('points')
|
||||||
|
messages.add_message(request, messages.INFO, 'Данные успешно импортированы')
|
||||||
|
except Exception as e:
|
||||||
|
messages.add_message(request, messages.ERROR, f'Ошибка импорта: {e}')
|
||||||
|
return HttpResponseRedirect('/admin/')
|
||||||
|
|
||||||
|
@action(detail=False, methods=['get'])
|
||||||
|
def naming(self, request):
|
||||||
|
return Response(get_model_naming(models.Point), status=status.HTTP_200_OK)
|
||||||
@ -0,0 +1,87 @@
|
|||||||
|
{% extends "admin/base_site.html" %}
|
||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% static "admin/css/dashboard.css" %}">{% endblock %}
|
||||||
|
|
||||||
|
{% block coltype %}colMS{% endblock %}
|
||||||
|
|
||||||
|
{% block bodyclass %}{{ block.super }} dashboard{% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}{% endblock %}
|
||||||
|
|
||||||
|
{% block nav-sidebar %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div id="content-main">
|
||||||
|
{% include "admin/app_list.html" with app_list=app_list show_changelinks=True %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block sidebar %}
|
||||||
|
|
||||||
|
<div id="content-related">
|
||||||
|
<div class="module" id="recent-actions-module">
|
||||||
|
<h2>{% translate 'Recent actions' %}</h2>
|
||||||
|
<h3>{% translate 'My actions' %}</h3>
|
||||||
|
{% load log %}
|
||||||
|
{% get_admin_log 10 as admin_log for_user user %}
|
||||||
|
{% if not admin_log %}
|
||||||
|
<p>{% translate 'None available' %}</p>
|
||||||
|
{% else %}
|
||||||
|
<ul class="actionlist">
|
||||||
|
{% for entry in admin_log %}
|
||||||
|
<li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">
|
||||||
|
{% if entry.is_deletion or not entry.get_admin_url %}
|
||||||
|
{{ entry.object_repr }}
|
||||||
|
{% else %}
|
||||||
|
<a href="{{ entry.get_admin_url }}">{{ entry.object_repr }}</a>
|
||||||
|
{% endif %}
|
||||||
|
<br>
|
||||||
|
{% if entry.content_type %}
|
||||||
|
<span class="mini quiet">{% filter capfirst %}{{ entry.content_type.name }}{% endfilter %}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="mini quiet">{% translate 'Unknown content' %}</span>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<h3>Обновить файл точек</h3>
|
||||||
|
<form method="post" action="/api/points/file_import/" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="file" name="file" accept=".xlsx">
|
||||||
|
<input type="submit" value="Отправить">
|
||||||
|
</form>
|
||||||
|
<h3>Обновить файл полигонов</h3>
|
||||||
|
<form method="post" action="/api/polygons/file_import/" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="file" name="file" accept=".zip">
|
||||||
|
<input type="submit" value="Отправить">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.change_city{
|
||||||
|
appearance: none;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #D7DA49;
|
||||||
|
color: #fff;
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.change_city:hover{
|
||||||
|
appearance: none;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #666;
|
||||||
|
color: #fff;
|
||||||
|
padding: 8px 16px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
Loading…
Reference in new issue