Compare commits

..

4 Commits

  1. 3
      LICENSE
  2. BIN
      Logo.png
  3. 101
      Logo.svg
  4. BIN
      LogoWhite.png
  5. BIN
      LogoWhiteSmall.png
  6. 3
      cms/__init__.py
  7. 15
      cms/celery.py
  8. 45
      cms/forms.py
  9. 15
      cms/models.py
  10. 19
      cms/tasks.py
  11. 147
      cms/templates/articles/new.html
  12. 20
      cms/templates/articles/planned.html
  13. 27
      cms/urls.py
  14. 173
      cms/views.py
  15. 315
      crossposting_backend/settings.py
  16. 32
      requirements.txt

3
LICENSE

@ -0,0 +1,3 @@
Этот продукт является ОБЩЕСТВЕННЫМ ДОСТОЯНИЕМ и может быть использован КАК ЕСТЬ, со всеми достоинствами и недостатками, полностью или частично, кем угодно и в каких угодно целях БЕЗ КАКИХ-ЛИБО ОГРАНИЧЕНИЙ.
This product is PUBLIC DOMAIN and may be used AS IS, with all advantages and faults, in whole or in part, by anyone for any purpose, WITHOUT ANY CONDITIONS.

BIN
Logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

101
Logo.svg

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="210mm"
height="297mm"
viewBox="0 0 210 297"
version="1.1"
id="svg5"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
sodipodi:docname="LogoCrossPosting.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="2"
inkscape:cx="273.25"
inkscape:cy="428.25"
inkscape:window-width="1366"
inkscape:window-height="704"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2" />
<g
inkscape:label="Слой 1"
inkscape:groupmode="layer"
id="layer1">
<circle
id="path13483-1-8-1"
cx="75.798073"
cy="-104.49218"
r="3.5844672"
style="fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
transform="scale(1,-1)" />
<circle
id="path13483-1-8-1-3"
cx="61.192924"
cy="-104.20211"
r="3.5844672"
style="fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
transform="scale(1,-1)" />
<circle
id="path13483-1-8-1-7"
cx="61.699493"
cy="-119.37672"
r="3.5844672"
style="fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
transform="scale(1,-1)" />
<path
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 64.149924,116.09775 10.033647,-9.60987"
id="path14457-0-6"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 61.183361,107.24427 v 9.32318"
id="path14457-2"
sodipodi:nodetypes="cc" />
<circle
id="path13483-1-8-1-5"
cx="75.844719"
cy="134.38693"
r="3.5844672"
style="fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<circle
id="path13483-1-8-1-3-3"
cx="61.239571"
cy="134.67699"
r="3.5844672"
style="fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 64.196572,122.78136 10.033647,9.60987"
id="path14457-0-6-9"
sodipodi:nodetypes="cc" />
<path
style="fill:none;stroke:#000000;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 61.230009,131.63484 v -9.32318"
id="path14457-2-3"
sodipodi:nodetypes="cc" />
<circle
style="fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path15124"
cx="68.566109"
cy="119.2259"
r="22.978039" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
LogoWhite.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

BIN
LogoWhiteSmall.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

3
cms/__init__.py

@ -1,3 +0,0 @@
from .celery import app as celery_app
__all__ = ('celery_app')

15
cms/celery.py

@ -1,15 +0,0 @@
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'crossposting_backend.settings')
CELERY_TIMEZONE = 'Europe/Moscow'
app = Celery('cms')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
@app.task(bind=True)
def debug_task(self):
print(f'Requestgit {self.request!r}')

45
cms/forms.py

@ -1,23 +1,22 @@
from django import forms from django import forms
from django.contrib.auth import models as auth_models from django.contrib.auth import models as auth_models
from .models import Article from .models import Article
class ArticleForm(forms.ModelForm): class ArticleForm(forms.ModelForm):
link_widget = forms.TextInput(attrs={'placeholder': 'Введите ссылку новости'}) link_widget = forms.TextInput(attrs={'placeholder': 'Введите ссылку новости'})
link = forms.CharField(widget=link_widget) link = forms.CharField(widget=link_widget)
publication_time = forms.DateTimeField(widget=forms.DateTimeInput(attrs={'type': 'datetime-local'}), required=False)
class Meta:
class Meta: model = Article
model = Article fields = ('body', 'link',)
fields = ('body', 'link', 'publication_time')
class UserForm(forms.ModelForm):
class UserForm(forms.ModelForm): class Meta:
class Meta: model = auth_models.User
model = auth_models.User fields = ('username', 'password',)
fields = ('username', 'password',) widgets = {
widgets = { 'password': forms.PasswordInput(),
'password': forms.PasswordInput(), }
}

15
cms/models.py

@ -1,9 +1,6 @@
from django.db import models from django.db import models
class Article(models.Model): class Article(models.Model):
id = models.BigAutoField(primary_key=True) body = models.TextField(null=False)
body = models.TextField(null=False) link = models.CharField(max_length=200, null=False)
link = models.CharField(max_length=200, null=False)
publication_time = models.DateTimeField(blank=True, null=True)
is_published = models.BooleanField(default=False)

19
cms/tasks.py

@ -1,19 +0,0 @@
from cms import promoters
from cms.models import Article
from celery import shared_task
@shared_task
def promote_post(article_id):
article = Article.objects.get(id=article_id)
article.is_published = True
article.save()
marketer = promoters.Marketer(article)
marketer.promote()
@shared_task
def delayed_post(article_id, publication_time):
article = Article.objects.get(id=article_id)
celery_task = promote_post.apply_async(args=[article.id], eta=publication_time)
return celery_task.id

147
cms/templates/articles/new.html

@ -1,82 +1,67 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load bootstrap5 %} {% load bootstrap5 %}
{% block content %} {% block content %}
<div class="container"> <div class="container">
<div class="row my-5"> <div class="row my-5">
<div class="col-md-12"> <div class="col-md-12">
<h1>Заполните данные статьи для продвижения в соц. сетях</h1> <h1>Заполните данные статьи для продвижения в соц. сетях</h1>
<form <form
method="post" method="post"
enctype="application/x-www-form-urlencoded" enctype="application/x-www-form-urlencoded"
action="{% url 'create-article' %}" action="{% url 'create-article' %}"
class="form" class="form"
> >
{% csrf_token %} {% csrf_token %}
{% bootstrap_form new_article_form %} {% bootstrap_form new_article_form %}
{% buttons %} {% buttons %}
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<button <button
class="btn btn-primary" class="btn btn-primary"
type="submit" type="submit"
id="submit-button"> disabled="disabled"
{% if new_article_form.publication_time.value %} >
disabled="disabled" Продвинуть
{% endif %} </button>
{% if new_article_form.publication_time.value %} </div>
Запланировать
{% else %} <div id="vkShare" class="col"></div>
Опубликовать сейчас </div>
{% endif %}
</button> {% endbuttons %}
&nbsp; </form>
<a href="{% url 'planned' %}" class="btn btn-primary">Список отложенных публикаций</a> </div>
</div> </div>
<script> </div>
document.getElementById("id_publication_time").addEventListener("change", function() { {% endblock content %}
var submitButton = document.getElementById("submit-button"); {% block extra_scripts %}
if (this.value) { <script type="text/javascript">
submitButton.innerHTML = "Запланировать"; let submitBtn = null
} else { const enableSubmitBtn = () => {
submitButton.innerHTML = "Опубликовать"; submitBtn.disabled = false
} }
}); const appendShare = (e) => {
</script> submitBtn.disabled = true
<div id="vkShare" class="col"></div> const articleLink = e.target.value;
</div> const gen = {
{% endbuttons %} url: articleLink
</form> }
</div> const buttonType = {
</div> type: "custom",
</div> text: '<img src="https://vk.com/images/share_32_2x.png" width="32" height="32" alt="share icon" />'
{% endblock content %} }
{% block extra_scripts %} document.getElementById('vkShare').innerHTML = VK.Share.button(gen, buttonType)
<script type="text/javascript"> const vkButtons = document.querySelectorAll('a[href^="//vk.com/"]')
let submitBtn = null vkButtons.forEach((vkBtn) => vkBtn.addEventListener('click', enableSubmitBtn))
const enableSubmitBtn = () => { }
submitBtn.disabled = false const main = () => {
} submitBtn = document.querySelector('button[type="submit"]')
const appendShare = (e) => {
submitBtn.disabled = true const linkInput = document.querySelector('[name="link"]');
const articleLink = e.target.value; linkInput.addEventListener('input', appendShare)
const gen = { linkInput.addEventListener('paste', appendShare)
url: articleLink }
} window.addEventListener('DOMContentLoaded', main)
const buttonType = { </script>
type: "custom",
text: '<img src="https://vk.com/images/share_32_2x.png" width="32" height="32" alt="share icon" />'
}
document.getElementById('vkShare').innerHTML = VK.Share.button(gen, buttonType)
const vkButtons = document.querySelectorAll('a[href^="//vk.com/"]')
vkButtons.forEach((vkBtn) => vkBtn.addEventListener('click', enableSubmitBtn))
}
const main = () => {
submitBtn = document.querySelector('button[type="submit"]')
const linkInput = document.querySelector('[name="link"]');
linkInput.addEventListener('input', appendShare)
linkInput.addEventListener('paste', appendShare)
}
window.addEventListener('DOMContentLoaded', main)
</script>
{% endblock %} {% endblock %}

20
cms/templates/articles/planned.html

@ -1,20 +0,0 @@
{% extends 'base.html' %}
{% load bootstrap5 %}
{% block content %}
<div class="container">
<a href="{% url 'new-article' %}" class="btn btn-primary">Вернуться</a>
&nbsp;
{% for article in post %}
<div class="card mb-3">
<div class="card-body">
<p class="card-text">{{ article.body }}</p>
<a href="{{ article.link }}">{{ article.link }}</a>
<p class="card-text"><small class="text-muted">Дата публикации: {{ article.publication_time }}</small></p>
{% if user.is_authenticated %}
<a href="{% url 'article_delete' article.id %}" class="btn btn-danger">Удалить</a>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endblock content %}

27
cms/urls.py

@ -1,14 +1,13 @@
from django.urls import path from django.urls import path
from .views import ArticleView, new_article, AuthenticationView, plannedView, articleDelete
from .views import ArticleView, new_article, AuthenticationView
urlpatterns = [
path('articles/', ArticleView.as_view(), name='create-article'), urlpatterns = [
path('articles/new/', new_article, name='new-article'), path('articles/', ArticleView.as_view(), name='create-article'),
path( path('articles/new/', new_article, name='new-article'),
'', path(
AuthenticationView.as_view(), '',
name='authenticate' AuthenticationView.as_view(),
), name='authenticate'
path('articles/planned/', plannedView, name='planned'), )
path('articles/article_delete/<int:id>/', articleDelete, name='article_delete'), ]
]

173
cms/views.py

@ -1,102 +1,71 @@
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import authenticate, login from django.contrib.auth import authenticate, login
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpRequest, HttpResponseRedirect from django.http import HttpRequest, HttpResponseRedirect
from django.shortcuts import render, redirect from django.shortcuts import render
from django.urls import reverse from django.urls import reverse
from django.views import View from django.views import View
from requests import request
from cms import promoters
from cms import promoters from cms.forms import ArticleForm, UserForm
from cms.forms import ArticleForm, UserForm from cms.models import Article
from cms.models import Article
from cms.tasks import delayed_post
from datetime import datetime, timezone class ArticleView(LoginRequiredMixin, View):
def post(self, request: HttpRequest):
post_data = request.POST
class ArticleView(LoginRequiredMixin, View): article = Article.objects.create(body=post_data['body'],
def post(self, request: HttpRequest): link=post_data['link'])
post_data = request.POST marketer = promoters.Marketer(article)
if 'publication_time' not in post_data or post_data['publication_time'] == "": try:
# Значение publication_time не указано marketer.promote()
article = Article.objects.create(body=post_data['body'], message_type = messages.SUCCESS
link=post_data['link'], message_text = 'Продвижение статьи прошло успешно'
publication_time=datetime.now()) except promoters.PromoteError as exc:
message_type = messages.ERROR
marketer = promoters.Marketer(article) message_text = 'Произошла ошибка: %s' % str(exc)
try: messages.add_message(request=request,
marketer.promote() level=message_type,
article.is_published = 1 message=message_text)
message_type = messages.SUCCESS return HttpResponseRedirect(reverse('new-article'))
message_text = 'Продвижение статьи прошло успешно'
article.is_published = True
article.save() @login_required
except promoters.PromoteError as exc: def new_article(request):
message_type = messages.ERROR article_form = ArticleForm()
message_text = 'Произошла ошибка: %s' % str(exc) article_context = {
messages.add_message(request=request, 'new_article_form': article_form
level=message_type, }
message=message_text) return render(request,
template_name='articles/new.html',
else: context=article_context)
# Значение publication_time указано
publication_time = post_data['publication_time']
publication_time = datetime.fromisoformat(publication_time) class AuthenticationView(View):
publication_time = publication_time.astimezone(timezone.utc) def get(self, request, *args, **kwargs):
article = Article.objects.create(body=post_data['body'], user_form = UserForm()
link=post_data['link'], auth_context = {
publication_time=publication_time) 'user_form': user_form,
}
delayed_post.apply_async(args=(article.id, publication_time), eta=publication_time) return render(request,
return HttpResponseRedirect(reverse('new-article')) 'user/sign_in.html',
context=auth_context)
@login_required def post(self, request, *args, **kwargs):
def new_article(request): username = request.POST['username']
article_form = ArticleForm() password = request.POST['password']
article_context = { authenticated_user = authenticate(username=username,
'new_article_form': article_form password=password)
} if authenticated_user is None:
return render(request, messages.add_message(request,
template_name='articles/new.html', messages.ERROR,
context=article_context) 'Неправильное имя пользователя и/или пароль')
return HttpResponseRedirect(reverse('authenticate'))
else:
class AuthenticationView(View): messages.add_message(request,
def get(self, request, *args, **kwargs): messages.SUCCESS,
user_form = UserForm() 'Поздравляю, вы вошли успешно')
auth_context = { login(request,
'user_form': user_form, user=authenticated_user)
} return HttpResponseRedirect(reverse('new-article'))
return render(request,
'user/sign_in.html',
context=auth_context)
def post(self, request, *args, **kwargs):
username = request.POST['username']
password = request.POST['password']
authenticated_user = authenticate(username=username,
password=password)
if authenticated_user is None:
messages.add_message(request,
messages.ERROR,
'Неправильное имя пользователя и/или пароль')
return HttpResponseRedirect(reverse('authenticate'))
else:
messages.add_message(request,
messages.SUCCESS,
'Поздравляю, вы вошли успешно')
login(request,
user=authenticated_user)
return HttpResponseRedirect(reverse('new-article'))
def plannedView(request):
data = Article.objects.filter(is_published=False)
return render(request, 'articles/planned.html', context={'post':data})
def articleDelete(request, id):
article = Article.objects.get(id=id)
article.delete()
return redirect('planned')

315
crossposting_backend/settings.py

@ -1,160 +1,155 @@
""" """
Django settings for crossposting_backend project. Django settings for crossposting_backend project.
Generated by 'django-admin startproject' using Django 4.1.4. Generated by 'django-admin startproject' using Django 4.1.4.
For more information on this file, see For more information on this file, see
https://docs.djangoproject.com/en/4.1/topics/settings/ https://docs.djangoproject.com/en/4.1/topics/settings/
For the full list of settings and their values, see For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.1/ref/settings/ https://docs.djangoproject.com/en/4.1/ref/settings/
""" """
from os import path, getenv from os import path, getenv
from pathlib import Path from pathlib import Path
import dotenv import dotenv
from celery.schedules import crontab from django.core import signing
from django.core import signing
from .private.settings import *
from .private.settings import *
def decode_env(env_key: str) -> str:
def decode_env(env_key: str) -> str: signer = signing.Signer(salt=SALT)
signer = signing.Signer(salt=SALT) signed_telegram_chat_id_dict = getenv(env_key)
signed_telegram_chat_id_dict = getenv(env_key) return signer.unsign_object(signed_telegram_chat_id_dict)[env_key]
return signer.unsign_object(signed_telegram_chat_id_dict)[env_key]
def return_env(env_key: str) -> str:
def return_env(env_key: str) -> str: """
""" Функция нужна как стратегия, если not ENV_ENCODED
Функция нужна как стратегия, если not ENV_ENCODED :param env_key:
:param env_key: :return:
:return: """
""" return getenv(env_key)
return getenv(env_key)
BASE_DIR = Path(__file__).resolve().parent.parent
BASE_DIR = Path(__file__).resolve().parent.parent env_file = path.join(BASE_DIR, '.env')
env_file = path.join(BASE_DIR, '.env')
dotenv.read_dotenv(env_file)
dotenv.read_dotenv(env_file)
promoter_env_keys = (
promoter_env_keys = ( 'TELEGRAM_BOT_TOKEN', 'TELEGRAM_CHAT_ID', 'JOOMLA_TOKEN',
'TELEGRAM_BOT_TOKEN', 'TELEGRAM_CHAT_ID', 'JOOMLA_TOKEN', 'VK_OWNER_ID', 'VK_TOKEN', 'OK_ACCESS_TOKEN', 'OK_APPLICATION_KEY',
'VK_OWNER_ID', 'VK_TOKEN', 'OK_ACCESS_TOKEN', 'OK_APPLICATION_KEY', 'OK_APPLICATION_SECRET_KEY', 'OK_GROUP_ID',
'OK_APPLICATION_SECRET_KEY', 'OK_GROUP_ID', )
) promoter_secrets = {}
promoter_secrets = {} if ENV_ENCODED:
if ENV_ENCODED: decode_strategy = decode_env
decode_strategy = decode_env else:
else: decode_strategy = return_env
decode_strategy = return_env
for promoter_env_key in promoter_env_keys:
for promoter_env_key in promoter_env_keys: promoter_secrets[promoter_env_key] = decode_strategy(promoter_env_key)
promoter_secrets[promoter_env_key] = decode_strategy(promoter_env_key)
# Build paths inside the project like this: BASE_DIR / 'subdir'.
# Build paths inside the project like this: BASE_DIR / 'subdir'.
# Quick-start development settings - unsuitable for production
# Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
LOGIN_URL = '/cms/'
LOGIN_URL = '/cms/'
# Application definition
# Application definition
INSTALLED_APPS = [
INSTALLED_APPS = [ 'django.contrib.admin',
'django.contrib.admin', 'django.contrib.auth',
'django.contrib.auth', 'django.contrib.contenttypes',
'django.contrib.contenttypes', 'django.contrib.sessions',
'django.contrib.sessions', 'django.contrib.messages',
'django.contrib.messages', 'django.contrib.staticfiles',
'django.contrib.staticfiles', 'cms',
'cms', 'bootstrap5',
'bootstrap5', ]
'django_celery_beat',
] MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
MIDDLEWARE = [ 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.security.SecurityMiddleware', 'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.middleware.common.CommonMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', ]
'django.middleware.clickjacking.XFrameOptionsMiddleware',
] ROOT_URLCONF = 'crossposting_backend.urls'
ROOT_URLCONF = 'crossposting_backend.urls' TEMPLATES = [
{
TEMPLATES = [ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
{ 'DIRS': [BASE_DIR / 'templates'],
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'APP_DIRS': True,
'DIRS': [BASE_DIR / 'templates'], 'OPTIONS': {
'APP_DIRS': True, 'context_processors': [
'OPTIONS': { 'django.template.context_processors.debug',
'context_processors': [ 'django.template.context_processors.request',
'django.template.context_processors.debug', 'django.contrib.auth.context_processors.auth',
'django.template.context_processors.request', 'django.contrib.messages.context_processors.messages',
'django.contrib.auth.context_processors.auth', ],
'django.contrib.messages.context_processors.messages', },
], },
}, ]
},
] WSGI_APPLICATION = 'crossposting_backend.wsgi.application'
WSGI_APPLICATION = 'crossposting_backend.wsgi.application' # Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases DATABASES = {
'default': {
DATABASES = { 'ENGINE': 'django.db.backends.sqlite3',
'default': { 'NAME': BASE_DIR / 'db.sqlite3',
'ENGINE': 'django.db.backends.sqlite3', }
'NAME': BASE_DIR / 'db.sqlite3', }
}
} # Password validation
# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
# Password validation
# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [
{
AUTH_PASSWORD_VALIDATORS = [ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
{ },
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', {
}, 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
{ },
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', {
}, 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
{ },
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', {
}, 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
{ },
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', ]
},
] # Internationalization
# https://docs.djangoproject.com/en/4.1/topics/i18n/
# Internationalization
# https://docs.djangoproject.com/en/4.1/topics/i18n/ LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC'
TIME_ZONE = 'Europe/Moscow' USE_I18N = True
USE_I18N = True USE_TZ = True
USE_TZ = True # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.1/howto/static-files/
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.1/howto/static-files/ STATIC_URL = 'static/'
STATIC_ROOT = path.join(BASE_DIR, 'static')
STATIC_URL = 'static/'
STATIC_ROOT = path.join(BASE_DIR, 'static') # Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
# Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
CELERY_BROKER_URL = 'redis://localhost:6379/'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/'

32
requirements.txt

@ -1,18 +1,14 @@
asgiref==3.5.2 asgiref==3.5.2
beautifulsoup4==4.11.1 beautifulsoup4==4.11.1
certifi==2022.12.7 certifi==2022.12.7
charset-normalizer==2.1.1 charset-normalizer==2.1.1
Django==4.1.4 Django==4.1.4
django-bootstrap-v5==1.0.11 django-bootstrap-v5==1.0.11
django-dotenv==1.4.2 django-dotenv==1.4.2
idna==3.4 idna==3.4
ok-api==1.0.1 ok-api==1.0.1
requests==2.28.1 requests==2.28.1
soupsieve==2.3.2.post1 soupsieve==2.3.2.post1
sqlparse==0.4.3 sqlparse==0.4.3
urllib3==1.26.13 urllib3==1.26.13
vk-api==11.9.9 vk-api==11.9.9
celery==5.3.6
django-celery-beat==2.6.0
redis==5.0.4

Loading…
Cancel
Save