Fynjy
8 months ago
11 changed files with 470 additions and 347 deletions
@ -0,0 +1,3 @@ |
|||||||
|
from .celery import app as celery_app |
||||||
|
|
||||||
|
__all__ = ('celery_app') |
@ -0,0 +1,21 @@ |
|||||||
|
from __future__ import absolute_import, unicode_literals |
||||||
|
import os |
||||||
|
from celery import Celery |
||||||
|
from celery.schedules import crontab |
||||||
|
from crossposting_backend.tasks import delayed_post |
||||||
|
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'crossposting_backend') |
||||||
|
|
||||||
|
app = Celery('crossposting_backend') |
||||||
|
app.config_from_object('django.conf:settings', namespace='CELERY') |
||||||
|
app.autodiscover_tasks() |
||||||
|
|
||||||
|
CELERY_BEAT_SCHEDULE = { |
||||||
|
# Задача, которая будет выполнять отложенные публикации |
||||||
|
'post-articles': { |
||||||
|
'task': 'crossposting_backend.tasks.delayed_post', |
||||||
|
'schedule': crontab(minute=0, hour='*'), # Запускать каждую минуту |
||||||
|
'args': () # Аргументы задачи, в данном случае их нет |
||||||
|
} |
||||||
|
} |
@ -1,22 +1,23 @@ |
|||||||
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: |
|
||||||
model = Article |
class Meta: |
||||||
fields = ('body', 'link',) |
model = Article |
||||||
|
fields = ('body', 'link', 'publication_time') |
||||||
|
|
||||||
class UserForm(forms.ModelForm): |
|
||||||
class Meta: |
class UserForm(forms.ModelForm): |
||||||
model = auth_models.User |
class Meta: |
||||||
fields = ('username', 'password',) |
model = auth_models.User |
||||||
widgets = { |
fields = ('username', 'password',) |
||||||
'password': forms.PasswordInput(), |
widgets = { |
||||||
} |
'password': forms.PasswordInput(), |
||||||
|
} |
||||||
|
@ -1,6 +1,9 @@ |
|||||||
from django.db import models |
from django.db import models |
||||||
|
|
||||||
|
|
||||||
class Article(models.Model): |
class Article(models.Model): |
||||||
body = models.TextField(null=False) |
id = models.BigAutoField(primary_key=True) |
||||||
link = models.CharField(max_length=200, null=False) |
body = models.TextField(null=False) |
||||||
|
link = models.CharField(max_length=200, null=False) |
||||||
|
publication_time = models.DateTimeField(blank=True, null=True) |
||||||
|
is_published = models.BooleanField(default=False) |
@ -0,0 +1,19 @@ |
|||||||
|
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 |
@ -1,67 +1,82 @@ |
|||||||
{% 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" |
||||||
disabled="disabled" |
id="submit-button"> |
||||||
> |
{% if new_article_form.publication_time.value %} |
||||||
Продвинуть |
disabled="disabled" |
||||||
</button> |
{% endif %} |
||||||
</div> |
{% if new_article_form.publication_time.value %} |
||||||
|
Запланировать |
||||||
<div id="vkShare" class="col"></div> |
{% else %} |
||||||
</div> |
Опубликовать сейчас |
||||||
|
{% endif %} |
||||||
{% endbuttons %} |
</button> |
||||||
</form> |
|
||||||
</div> |
<a href="{% url 'planned' %}" class="btn btn-primary">Список отложенных публикаций</a> |
||||||
</div> |
</div> |
||||||
</div> |
<script> |
||||||
{% endblock content %} |
document.getElementById("id_publication_time").addEventListener("change", function() { |
||||||
{% block extra_scripts %} |
var submitButton = document.getElementById("submit-button"); |
||||||
<script type="text/javascript"> |
if (this.value) { |
||||||
let submitBtn = null |
submitButton.innerHTML = "Запланировать"; |
||||||
const enableSubmitBtn = () => { |
} else { |
||||||
submitBtn.disabled = false |
submitButton.innerHTML = "Опубликовать"; |
||||||
} |
} |
||||||
const appendShare = (e) => { |
}); |
||||||
submitBtn.disabled = true |
</script> |
||||||
|
|
||||||
const articleLink = e.target.value; |
<div id="vkShare" class="col"></div> |
||||||
const gen = { |
</div> |
||||||
url: articleLink |
{% endbuttons %} |
||||||
} |
</form> |
||||||
const buttonType = { |
</div> |
||||||
type: "custom", |
</div> |
||||||
text: '<img src="https://vk.com/images/share_32_2x.png" width="32" height="32" alt="share icon" />' |
</div> |
||||||
} |
{% endblock content %} |
||||||
document.getElementById('vkShare').innerHTML = VK.Share.button(gen, buttonType) |
{% block extra_scripts %} |
||||||
const vkButtons = document.querySelectorAll('a[href^="//vk.com/"]') |
<script type="text/javascript"> |
||||||
vkButtons.forEach((vkBtn) => vkBtn.addEventListener('click', enableSubmitBtn)) |
let submitBtn = null |
||||||
} |
const enableSubmitBtn = () => { |
||||||
const main = () => { |
submitBtn.disabled = false |
||||||
submitBtn = document.querySelector('button[type="submit"]') |
} |
||||||
|
const appendShare = (e) => { |
||||||
const linkInput = document.querySelector('[name="link"]'); |
submitBtn.disabled = true |
||||||
linkInput.addEventListener('input', appendShare) |
const articleLink = e.target.value; |
||||||
linkInput.addEventListener('paste', appendShare) |
const gen = { |
||||||
} |
url: articleLink |
||||||
window.addEventListener('DOMContentLoaded', main) |
} |
||||||
</script> |
const buttonType = { |
||||||
|
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 %} |
@ -0,0 +1,20 @@ |
|||||||
|
{% extends 'base.html' %} |
||||||
|
{% load bootstrap5 %} |
||||||
|
{% block content %} |
||||||
|
<div class="container"> |
||||||
|
<a href="{% url 'new-article' %}" class="btn btn-primary">Вернуться</a> |
||||||
|
|
||||||
|
{% 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 %} |
@ -1,13 +1,14 @@ |
|||||||
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 = [ |
||||||
urlpatterns = [ |
path('articles/', ArticleView.as_view(), name='create-article'), |
||||||
path('articles/', ArticleView.as_view(), name='create-article'), |
path('articles/new/', new_article, name='new-article'), |
||||||
path('articles/new/', new_article, name='new-article'), |
path( |
||||||
path( |
'', |
||||||
'', |
AuthenticationView.as_view(), |
||||||
AuthenticationView.as_view(), |
name='authenticate' |
||||||
name='authenticate' |
), |
||||||
) |
path('articles/planned/', plannedView, name='planned'), |
||||||
] |
path('articles/article_delete/<int:id>/', articleDelete, name='article_delete'), |
||||||
|
] |
@ -1,71 +1,102 @@ |
|||||||
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 |
from django.shortcuts import render, redirect |
||||||
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.forms import ArticleForm, UserForm |
from cms import promoters |
||||||
from cms.models import Article |
from cms.forms import ArticleForm, UserForm |
||||||
|
from cms.models import Article |
||||||
|
from cms.tasks import delayed_post |
||||||
class ArticleView(LoginRequiredMixin, View): |
from datetime import datetime, timezone |
||||||
def post(self, request: HttpRequest): |
|
||||||
post_data = request.POST |
|
||||||
article = Article.objects.create(body=post_data['body'], |
class ArticleView(LoginRequiredMixin, View): |
||||||
link=post_data['link']) |
def post(self, request: HttpRequest): |
||||||
marketer = promoters.Marketer(article) |
post_data = request.POST |
||||||
try: |
if 'publication_time' not in post_data or post_data['publication_time'] == "": |
||||||
marketer.promote() |
# Значение publication_time не указано |
||||||
message_type = messages.SUCCESS |
article = Article.objects.create(body=post_data['body'], |
||||||
message_text = 'Продвижение статьи прошло успешно' |
link=post_data['link'], |
||||||
except promoters.PromoteError as exc: |
publication_time=datetime.now()) |
||||||
message_type = messages.ERROR |
|
||||||
message_text = 'Произошла ошибка: %s' % str(exc) |
marketer = promoters.Marketer(article) |
||||||
messages.add_message(request=request, |
try: |
||||||
level=message_type, |
marketer.promote() |
||||||
message=message_text) |
article.is_published = 1 |
||||||
return HttpResponseRedirect(reverse('new-article')) |
message_type = messages.SUCCESS |
||||||
|
message_text = 'Продвижение статьи прошло успешно' |
||||||
|
article.is_published = True |
||||||
@login_required |
article.save() |
||||||
def new_article(request): |
except promoters.PromoteError as exc: |
||||||
article_form = ArticleForm() |
message_type = messages.ERROR |
||||||
article_context = { |
message_text = 'Произошла ошибка: %s' % str(exc) |
||||||
'new_article_form': article_form |
messages.add_message(request=request, |
||||||
} |
level=message_type, |
||||||
return render(request, |
message=message_text) |
||||||
template_name='articles/new.html', |
|
||||||
context=article_context) |
else: |
||||||
|
# Значение publication_time указано |
||||||
|
publication_time = post_data['publication_time'] |
||||||
class AuthenticationView(View): |
publication_time = datetime.fromisoformat(publication_time) |
||||||
def get(self, request, *args, **kwargs): |
publication_time = publication_time.astimezone(timezone.utc) |
||||||
user_form = UserForm() |
article = Article.objects.create(body=post_data['body'], |
||||||
auth_context = { |
link=post_data['link'], |
||||||
'user_form': user_form, |
publication_time=publication_time) |
||||||
} |
|
||||||
return render(request, |
delayed_post.apply_async(args=(article.id, publication_time), eta=publication_time) |
||||||
'user/sign_in.html', |
return HttpResponseRedirect(reverse('new-article')) |
||||||
context=auth_context) |
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs): |
@login_required |
||||||
username = request.POST['username'] |
def new_article(request): |
||||||
password = request.POST['password'] |
article_form = ArticleForm() |
||||||
authenticated_user = authenticate(username=username, |
article_context = { |
||||||
password=password) |
'new_article_form': article_form |
||||||
if authenticated_user is None: |
} |
||||||
messages.add_message(request, |
return render(request, |
||||||
messages.ERROR, |
template_name='articles/new.html', |
||||||
'Неправильное имя пользователя и/или пароль') |
context=article_context) |
||||||
return HttpResponseRedirect(reverse('authenticate')) |
|
||||||
else: |
|
||||||
messages.add_message(request, |
class AuthenticationView(View): |
||||||
messages.SUCCESS, |
def get(self, request, *args, **kwargs): |
||||||
'Поздравляю, вы вошли успешно') |
user_form = UserForm() |
||||||
login(request, |
auth_context = { |
||||||
user=authenticated_user) |
'user_form': user_form, |
||||||
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') |
@ -1,155 +1,160 @@ |
|||||||
""" |
""" |
||||||
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 django.core import signing |
from celery.schedules import crontab |
||||||
|
from django.core import signing |
||||||
from .private.settings import * |
|
||||||
|
from .private.settings import * |
||||||
|
|
||||||
def decode_env(env_key: str) -> str: |
|
||||||
signer = signing.Signer(salt=SALT) |
def decode_env(env_key: str) -> str: |
||||||
signed_telegram_chat_id_dict = getenv(env_key) |
signer = signing.Signer(salt=SALT) |
||||||
return signer.unsign_object(signed_telegram_chat_id_dict)[env_key] |
signed_telegram_chat_id_dict = getenv(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 |
""" |
||||||
:param env_key: |
Функция нужна как стратегия, если not ENV_ENCODED |
||||||
:return: |
:param env_key: |
||||||
""" |
:return: |
||||||
return getenv(env_key) |
""" |
||||||
|
return getenv(env_key) |
||||||
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent |
|
||||||
env_file = path.join(BASE_DIR, '.env') |
BASE_DIR = Path(__file__).resolve().parent.parent |
||||||
|
env_file = path.join(BASE_DIR, '.env') |
||||||
dotenv.read_dotenv(env_file) |
|
||||||
|
dotenv.read_dotenv(env_file) |
||||||
promoter_env_keys = ( |
|
||||||
'TELEGRAM_BOT_TOKEN', 'TELEGRAM_CHAT_ID', 'JOOMLA_TOKEN', |
promoter_env_keys = ( |
||||||
'VK_OWNER_ID', 'VK_TOKEN', 'OK_ACCESS_TOKEN', 'OK_APPLICATION_KEY', |
'TELEGRAM_BOT_TOKEN', 'TELEGRAM_CHAT_ID', 'JOOMLA_TOKEN', |
||||||
'OK_APPLICATION_SECRET_KEY', 'OK_GROUP_ID', |
'VK_OWNER_ID', 'VK_TOKEN', 'OK_ACCESS_TOKEN', 'OK_APPLICATION_KEY', |
||||||
) |
'OK_APPLICATION_SECRET_KEY', 'OK_GROUP_ID', |
||||||
promoter_secrets = {} |
) |
||||||
if ENV_ENCODED: |
promoter_secrets = {} |
||||||
decode_strategy = decode_env |
if ENV_ENCODED: |
||||||
else: |
decode_strategy = decode_env |
||||||
decode_strategy = return_env |
else: |
||||||
|
decode_strategy = return_env |
||||||
for promoter_env_key in promoter_env_keys: |
|
||||||
promoter_secrets[promoter_env_key] = decode_strategy(promoter_env_key) |
for promoter_env_key in promoter_env_keys: |
||||||
|
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 |
|
||||||
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ |
# Quick-start development settings - unsuitable for production |
||||||
|
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ |
||||||
|
|
||||||
LOGIN_URL = '/cms/' |
|
||||||
|
LOGIN_URL = '/cms/' |
||||||
# Application definition |
|
||||||
|
# Application definition |
||||||
INSTALLED_APPS = [ |
|
||||||
'django.contrib.admin', |
INSTALLED_APPS = [ |
||||||
'django.contrib.auth', |
'django.contrib.admin', |
||||||
'django.contrib.contenttypes', |
'django.contrib.auth', |
||||||
'django.contrib.sessions', |
'django.contrib.contenttypes', |
||||||
'django.contrib.messages', |
'django.contrib.sessions', |
||||||
'django.contrib.staticfiles', |
'django.contrib.messages', |
||||||
'cms', |
'django.contrib.staticfiles', |
||||||
'bootstrap5', |
'cms', |
||||||
] |
'bootstrap5', |
||||||
|
'django_celery_beat', |
||||||
MIDDLEWARE = [ |
] |
||||||
'django.middleware.security.SecurityMiddleware', |
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware', |
MIDDLEWARE = [ |
||||||
'django.middleware.common.CommonMiddleware', |
'django.middleware.security.SecurityMiddleware', |
||||||
'django.middleware.csrf.CsrfViewMiddleware', |
'django.contrib.sessions.middleware.SessionMiddleware', |
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware', |
'django.middleware.common.CommonMiddleware', |
||||||
'django.contrib.messages.middleware.MessageMiddleware', |
'django.middleware.csrf.CsrfViewMiddleware', |
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware', |
'django.contrib.auth.middleware.AuthenticationMiddleware', |
||||||
] |
'django.contrib.messages.middleware.MessageMiddleware', |
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware', |
||||||
ROOT_URLCONF = 'crossposting_backend.urls' |
] |
||||||
|
|
||||||
TEMPLATES = [ |
ROOT_URLCONF = 'crossposting_backend.urls' |
||||||
{ |
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates', |
TEMPLATES = [ |
||||||
'DIRS': [BASE_DIR / 'templates'], |
{ |
||||||
'APP_DIRS': True, |
'BACKEND': 'django.template.backends.django.DjangoTemplates', |
||||||
'OPTIONS': { |
'DIRS': [BASE_DIR / 'templates'], |
||||||
'context_processors': [ |
'APP_DIRS': True, |
||||||
'django.template.context_processors.debug', |
'OPTIONS': { |
||||||
'django.template.context_processors.request', |
'context_processors': [ |
||||||
'django.contrib.auth.context_processors.auth', |
'django.template.context_processors.debug', |
||||||
'django.contrib.messages.context_processors.messages', |
'django.template.context_processors.request', |
||||||
], |
'django.contrib.auth.context_processors.auth', |
||||||
}, |
'django.contrib.messages.context_processors.messages', |
||||||
}, |
], |
||||||
] |
}, |
||||||
|
}, |
||||||
WSGI_APPLICATION = 'crossposting_backend.wsgi.application' |
] |
||||||
|
|
||||||
# Database |
WSGI_APPLICATION = 'crossposting_backend.wsgi.application' |
||||||
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases |
|
||||||
|
# Database |
||||||
DATABASES = { |
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases |
||||||
'default': { |
|
||||||
'ENGINE': 'django.db.backends.sqlite3', |
DATABASES = { |
||||||
'NAME': BASE_DIR / 'db.sqlite3', |
'default': { |
||||||
} |
'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 |
||||||
AUTH_PASSWORD_VALIDATORS = [ |
# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators |
||||||
{ |
|
||||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', |
AUTH_PASSWORD_VALIDATORS = [ |
||||||
}, |
{ |
||||||
{ |
'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 |
||||||
LANGUAGE_CODE = 'en-us' |
# https://docs.djangoproject.com/en/4.1/topics/i18n/ |
||||||
|
|
||||||
TIME_ZONE = 'UTC' |
LANGUAGE_CODE = 'en-us' |
||||||
|
|
||||||
USE_I18N = True |
TIME_ZONE = 'Europe/Moscow' |
||||||
|
|
||||||
USE_TZ = True |
USE_I18N = True |
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images) |
USE_TZ = True |
||||||
# https://docs.djangoproject.com/en/4.1/howto/static-files/ |
|
||||||
|
# Static files (CSS, JavaScript, Images) |
||||||
STATIC_URL = 'static/' |
# https://docs.djangoproject.com/en/4.1/howto/static-files/ |
||||||
STATIC_ROOT = path.join(BASE_DIR, 'static') |
|
||||||
|
STATIC_URL = 'static/' |
||||||
# Default primary key field type |
STATIC_ROOT = path.join(BASE_DIR, 'static') |
||||||
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field |
|
||||||
|
# Default primary key field type |
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' |
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field |
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' |
||||||
|
|
||||||
|
CELERY_BROKER_URL = 'redis://localhost:6379/' |
||||||
|
CELERY_RESULT_BACKEND = 'redis://localhost:6379/' |
@ -1,14 +1,18 @@ |
|||||||
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…
Reference in new issue