Compare commits

...

3 Commits

  1. 3
      cms/__init__.py
  2. 15
      cms/celery.py
  3. 3
      cms/forms.py
  4. 3
      cms/models.py
  5. 19
      cms/tasks.py
  6. 25
      cms/templates/articles/new.html
  7. 20
      cms/templates/articles/planned.html
  8. 7
      cms/urls.py
  9. 35
      cms/views.py
  10. 7
      crossposting_backend/settings.py
  11. 4
      requirements.txt

3
cms/__init__.py

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

15
cms/celery.py

@ -0,0 +1,15 @@
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}')

3
cms/forms.py

@ -7,10 +7,11 @@ 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):

3
cms/models.py

@ -2,5 +2,8 @@ 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

@ -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

25
cms/templates/articles/new.html

@ -19,15 +19,32 @@
<button <button
class="btn btn-primary" class="btn btn-primary"
type="submit" type="submit"
id="submit-button">
{% if new_article_form.publication_time.value %}
disabled="disabled" disabled="disabled"
> {% endif %}
Продвинуть {% if new_article_form.publication_time.value %}
Запланировать
{% else %}
Опубликовать сейчас
{% endif %}
</button> </button>
&nbsp;
<a href="{% url 'planned' %}" class="btn btn-primary">Список отложенных публикаций</a>
</div> </div>
<script>
document.getElementById("id_publication_time").addEventListener("change", function() {
var submitButton = document.getElementById("submit-button");
if (this.value) {
submitButton.innerHTML = "Запланировать";
} else {
submitButton.innerHTML = "Опубликовать";
}
});
</script>
<div id="vkShare" class="col"></div> <div id="vkShare" class="col"></div>
</div> </div>
{% endbuttons %} {% endbuttons %}
</form> </form>
</div> </div>
@ -42,7 +59,6 @@
} }
const appendShare = (e) => { const appendShare = (e) => {
submitBtn.disabled = true submitBtn.disabled = true
const articleLink = e.target.value; const articleLink = e.target.value;
const gen = { const gen = {
url: articleLink url: articleLink
@ -57,7 +73,6 @@
} }
const main = () => { const main = () => {
submitBtn = document.querySelector('button[type="submit"]') submitBtn = document.querySelector('button[type="submit"]')
const linkInput = document.querySelector('[name="link"]'); const linkInput = document.querySelector('[name="link"]');
linkInput.addEventListener('input', appendShare) linkInput.addEventListener('input', appendShare)
linkInput.addEventListener('paste', appendShare) linkInput.addEventListener('paste', appendShare)

20
cms/templates/articles/planned.html

@ -0,0 +1,20 @@
{% 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 %}

7
cms/urls.py

@ -1,6 +1,5 @@
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'),
@ -9,5 +8,7 @@ urlpatterns = [
'', '',
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'),
] ]

35
cms/views.py

@ -3,31 +3,52 @@ 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 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): class ArticleView(LoginRequiredMixin, View):
def post(self, request: HttpRequest): def post(self, request: HttpRequest):
post_data = request.POST post_data = request.POST
if 'publication_time' not in post_data or post_data['publication_time'] == "":
# Значение publication_time не указано
article = Article.objects.create(body=post_data['body'], article = Article.objects.create(body=post_data['body'],
link=post_data['link']) link=post_data['link'],
publication_time=datetime.now())
marketer = promoters.Marketer(article) marketer = promoters.Marketer(article)
try: try:
marketer.promote() marketer.promote()
article.is_published = 1
message_type = messages.SUCCESS message_type = messages.SUCCESS
message_text = 'Продвижение статьи прошло успешно' message_text = 'Продвижение статьи прошло успешно'
article.is_published = True
article.save()
except promoters.PromoteError as exc: except promoters.PromoteError as exc:
message_type = messages.ERROR message_type = messages.ERROR
message_text = 'Произошла ошибка: %s' % str(exc) message_text = 'Произошла ошибка: %s' % str(exc)
messages.add_message(request=request, messages.add_message(request=request,
level=message_type, level=message_type,
message=message_text) message=message_text)
else:
# Значение publication_time указано
publication_time = post_data['publication_time']
publication_time = datetime.fromisoformat(publication_time)
publication_time = publication_time.astimezone(timezone.utc)
article = Article.objects.create(body=post_data['body'],
link=post_data['link'],
publication_time=publication_time)
delayed_post.apply_async(args=(article.id, publication_time), eta=publication_time)
return HttpResponseRedirect(reverse('new-article')) return HttpResponseRedirect(reverse('new-article'))
@ -69,3 +90,13 @@ class AuthenticationView(View):
login(request, login(request,
user=authenticated_user) user=authenticated_user)
return HttpResponseRedirect(reverse('new-article')) 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')

7
crossposting_backend/settings.py

@ -14,6 +14,7 @@ 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 *
@ -72,6 +73,7 @@ INSTALLED_APPS = [
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'cms', 'cms',
'bootstrap5', 'bootstrap5',
'django_celery_beat',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@ -137,7 +139,7 @@ AUTH_PASSWORD_VALIDATORS = [
LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC' TIME_ZONE = 'Europe/Moscow'
USE_I18N = True USE_I18N = True
@ -153,3 +155,6 @@ STATIC_ROOT = path.join(BASE_DIR, 'static')
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field # 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/'

4
requirements.txt

@ -12,3 +12,7 @@ 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