delayed add url and search
This commit is contained in:
parent
9904586ea0
commit
dddbb034e5
@ -34,6 +34,7 @@ services:
|
|||||||
max-file: "5"
|
max-file: "5"
|
||||||
|
|
||||||
later42:
|
later42:
|
||||||
|
build: .
|
||||||
container_name: later42
|
container_name: later42
|
||||||
image: dntskdev/later42:master
|
image: dntskdev/later42:master
|
||||||
restart: always
|
restart: always
|
||||||
@ -59,6 +60,28 @@ services:
|
|||||||
- "traefik.http.routers.later42-opt.tls.certresolver=myresolver"
|
- "traefik.http.routers.later42-opt.tls.certresolver=myresolver"
|
||||||
- "extdns.cf.later42.hostname=later42.com"
|
- "extdns.cf.later42.hostname=later42.com"
|
||||||
|
|
||||||
|
# later42_tasks:
|
||||||
|
# build: .
|
||||||
|
# container_name: later42_tasks
|
||||||
|
# image: dntskdev/later42:master
|
||||||
|
# restart: always
|
||||||
|
# command: celery -A later42 worker --loglevel=info
|
||||||
|
# environment:
|
||||||
|
# SECRET: "ahth3chaquodahh6que8thie1EThe5Iephich8eikei2Uojaemae6gee0kaet4aush2aoqu0ruL9oGhaiR9luu7cohreH6lebo0v"
|
||||||
|
# DB_TYPE: postgres
|
||||||
|
# DB_HOST: later42db
|
||||||
|
# DB_NAME: later42
|
||||||
|
# DB_USER: later42
|
||||||
|
# DB_PASS: later42
|
||||||
|
# DOMAIN: later42.com
|
||||||
|
# READABILITY_HOST: http://ureadability:8080/
|
||||||
|
# REDIS_URL: redis://redis:6379
|
||||||
|
# logging:
|
||||||
|
# driver: json-file
|
||||||
|
# options:
|
||||||
|
# max-size: "10m"
|
||||||
|
# max-file: "5"
|
||||||
|
|
||||||
later42db:
|
later42db:
|
||||||
container_name: later42db
|
container_name: later42db
|
||||||
image: postgres
|
image: postgres
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
# Generated by Django 4.1.2 on 2022-11-14 12:28
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('later42', '0005_rename_articles_article'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='url',
|
||||||
|
name='content',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='url',
|
||||||
|
name='title',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='article',
|
||||||
|
name='short',
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='article',
|
||||||
|
name='title',
|
||||||
|
field=models.CharField(blank=True, max_length=2000, null=True),
|
||||||
|
),
|
||||||
|
]
|
@ -1,4 +1,3 @@
|
|||||||
from django.contrib.auth.models import User
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from later42.models.urls import URL
|
from later42.models.urls import URL
|
||||||
|
|
||||||
@ -7,3 +6,5 @@ class Article(models.Model):
|
|||||||
id = models.AutoField(auto_created=True, primary_key=True)
|
id = models.AutoField(auto_created=True, primary_key=True)
|
||||||
url = models.ForeignKey(URL, on_delete=models.CASCADE)
|
url = models.ForeignKey(URL, on_delete=models.CASCADE)
|
||||||
content = models.TextField(blank=True, null=True)
|
content = models.TextField(blank=True, null=True)
|
||||||
|
title = models.CharField(max_length=2000, blank=True, null=True)
|
||||||
|
short = models.TextField(blank=True, null=True)
|
||||||
|
@ -8,6 +8,4 @@ class URL(models.Model):
|
|||||||
id = models.AutoField(auto_created=True, primary_key=True)
|
id = models.AutoField(auto_created=True, primary_key=True)
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
url = models.CharField(max_length=2000)
|
url = models.CharField(max_length=2000)
|
||||||
title = models.CharField(max_length=2000)
|
|
||||||
archived = models.BooleanField(default=False)
|
archived = models.BooleanField(default=False)
|
||||||
content = models.TextField(blank=True, null=True)
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import pybrake
|
import pybrake
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from pybrake.middleware.celery import patch_celery
|
from pybrake.middleware.celery import patch_celery
|
||||||
from later42.models.urls import URL
|
from later42.models.urls import URL
|
||||||
from later42.models.article import Article
|
from later42.models.article import Article
|
||||||
@ -19,9 +20,17 @@ if AIRBRAKE_PROJECT_ID is not None and AIRBRAKE_PROJECT_KEY is not None:
|
|||||||
|
|
||||||
|
|
||||||
@shared_task()
|
@shared_task()
|
||||||
def get_url_content_task(id):
|
def get_url_content_task(url, user_id):
|
||||||
url = URL.objects.get(id=id)
|
print(url)
|
||||||
article = Article.objects.create(url=url)
|
print(user_id)
|
||||||
content = get_content(url.url)['rich_content']
|
user = User.objects.get(pk=int(user_id))
|
||||||
article.content = content
|
url_object = URL(url=url, user=user)
|
||||||
|
url_object.save()
|
||||||
|
|
||||||
|
data = get_content(url)
|
||||||
|
|
||||||
|
article = Article.objects.create(url=url_object)
|
||||||
|
article.content = data['rich_content']
|
||||||
|
article.title = data['title']
|
||||||
|
article.short = data['excerpt']
|
||||||
article.save()
|
article.save()
|
||||||
|
@ -2,52 +2,55 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="post-preview">
|
<div class="post-preview">
|
||||||
{% if urls|length > 0 %}
|
{% if data|length > 0 %}
|
||||||
{% for url in urls %}
|
{% for d in data %}
|
||||||
<!-- Post preview-->
|
<!-- Post preview-->
|
||||||
<div class="post-preview">
|
<div class="post-preview">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<a href="{% url 'reader' url.id %}">
|
<a href="{% url 'reader' d.url.id %}">
|
||||||
<h2 class="post-title">{{ url.title }}</h2>
|
<h2 class="post-title">{% if d.title %}{{ d.title }}{% else %}Без заголовка{% endif %}</h2>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<a href="/delete/{{ url.id }}"><span class="fa-regular fa-trash-can"></span></a>
|
<a href="/delete/{{ d.url.id }}"><span class="fa-regular fa-trash-can"></span></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if url.content %}
|
{% if d.short %}
|
||||||
<p class="post-meta">
|
<p class="post-meta">
|
||||||
{{ url.content }}
|
{{ d.short }}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="post-meta">
|
<p class="post-meta">
|
||||||
<a href="{{ url.url }}">
|
<a href="{{ d.url.url }}">
|
||||||
{{ url.url }}
|
{{ d.url.url }}
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<!-- Divider-->
|
<!-- Divider-->
|
||||||
<hr class="my-4" />
|
<hr class="my-4" />
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
{% if data.number %}
|
||||||
<div class="pagination container row justify-content-end">
|
<div class="pagination container row justify-content-end">
|
||||||
<div></div>
|
<div></div>
|
||||||
<div class="step-links col-auto">
|
<div class="step-links col-auto">
|
||||||
{% if urls.has_previous %}
|
{% if data.has_previous %}
|
||||||
<a href="?page=1">«</a>
|
<a href="?page=1">«</a>
|
||||||
<a href="?page={{ urls.previous_page_number }}">‹</a>
|
<a href="?page={{ data.previous_page_number }}">‹</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<span class="current">
|
<span class="current">
|
||||||
страница {{ urls.number }} из {{ urls.paginator.num_pages }}
|
страница {{ data.number }} из {{ data.paginator.num_pages }}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{% if urls.has_next %}
|
{% if data.has_next %}
|
||||||
<a href="?page={{ urls.next_page_number }}">›</a>
|
<a href="?page={{ data.next_page_number }}">›</a>
|
||||||
<a href="?page={{ urls.paginator.num_pages }}">»</a>
|
<a href="?page={{ data.paginator.num_pages }}">»</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<h2 class="post-title">У вас нет ссылок в архиве</h2>
|
<h2 class="post-title">У вас нет ссылок в архиве</h2>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -57,6 +57,7 @@
|
|||||||
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{% url 'about' %}">О нас</a></li>
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{% url 'about' %}">О нас</a></li>
|
||||||
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{% url 'profile' %}">Профиль</a></li>
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{% url 'profile' %}">Профиль</a></li>
|
||||||
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{% url 'archive' %}">Архив</a></li>
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{% url 'archive' %}">Архив</a></li>
|
||||||
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{% url 'search' %}">Поиск</a></li>
|
||||||
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{% url 'logout' %}">Выйти</a></li>
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{% url 'logout' %}">Выйти</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{% url 'signup' %}">Регистрация</a></li>
|
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="{% url 'signup' %}">Регистрация</a></li>
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if user.is_authenticated and urls|length > 0 %}
|
{% if user.is_authenticated and data|length > 0 %}
|
||||||
{% for url in urls %}
|
{% for d in data %}
|
||||||
<!-- Post preview-->
|
<!-- Post preview-->
|
||||||
<div class="post-preview">
|
<div class="post-preview">
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<a href="{% url 'reader' url.id %}">
|
<a href="{% url 'reader' d.url_id %}">
|
||||||
<h2 class="post-title">{{ url.title }}</h2>
|
<h2 class="post-title">{% if d.title %}{{ d.title }}{% else %}Без заголовка{% endif %}</h2>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
<a href="{% url 'archive_url' url.id %}"><span class="fa-regular fa-square-check"></span></a>
|
<a href="{% url 'archive_url' d.url_id %}"><span class="fa-regular fa-square-check"></span></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if url.content %}
|
{% if d.short %}
|
||||||
<p class="post-meta">
|
<p class="post-meta">
|
||||||
{{ url.content }}
|
{{ d.short }}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p class="post-meta">
|
<p class="post-meta">
|
||||||
{{ url.url|urlizetrunc:70 }}
|
{{ d.url.url|urlizetrunc:70 }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<!-- Divider-->
|
<!-- Divider-->
|
||||||
|
58
later42/templates/search.html
Normal file
58
later42/templates/search.html
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Поиск{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form action="{% url 'search' %}" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="text" name="search" placeholder="Что ищем?">
|
||||||
|
<input type="submit" value="Искать" />
|
||||||
|
</form>
|
||||||
|
<br />
|
||||||
|
{% for d in data %}
|
||||||
|
<!-- Post preview-->
|
||||||
|
<div class="post-preview">
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<div class="p-2">
|
||||||
|
<a href="{% url 'reader' d.url.id %}">
|
||||||
|
<h2 class="post-title">{{ d.title }}</h2>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="p-2">
|
||||||
|
<a href="{% url 'archive_url' d.url.id %}"><span class="fa-regular fa-square-check"></span></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if d.short %}
|
||||||
|
<p class="post-meta">
|
||||||
|
{{ d.short }}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
<p class="post-meta">
|
||||||
|
{{ d.url.url|urlizetrunc:70 }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- Divider-->
|
||||||
|
<hr class="my-4" />
|
||||||
|
{% endfor %}
|
||||||
|
{% if data.number %}
|
||||||
|
<div class="pagination container row justify-content-end">
|
||||||
|
<div></div>
|
||||||
|
<div class="step-links col-auto">
|
||||||
|
{% if data.has_previous %}
|
||||||
|
<a href="?page=1">«</a>
|
||||||
|
<a href="?page={{ data.previous_page_number }}">‹</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<span class="current">
|
||||||
|
страница {{ data.number }} из {{ data.paginator.num_pages }}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{% if data.has_next %}
|
||||||
|
<a href="?page={{ data.next_page_number }}">›</a>
|
||||||
|
<a href="?page={{ data.paginator.num_pages }}">»</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
@ -22,7 +22,7 @@ from django.contrib.auth.models import User
|
|||||||
from rest_framework import routers, serializers, viewsets
|
from rest_framework import routers, serializers, viewsets
|
||||||
|
|
||||||
# from later42.forms import CustomLoginForm
|
# from later42.forms import CustomLoginForm
|
||||||
from later42.views import account_activation, index, profile, api, api_token, reader, signup, about
|
from later42.views import account_activation, index, profile, api, api_token, reader, search, signup, about
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.HyperlinkedModelSerializer):
|
class UserSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
@ -54,4 +54,5 @@ urlpatterns = [
|
|||||||
path('archive/', index.archive, name='archive'),
|
path('archive/', index.archive, name='archive'),
|
||||||
path('archive/<int:url_id>', index.archive, name='archive_url'),
|
path('archive/<int:url_id>', index.archive, name='archive_url'),
|
||||||
path('reader/<int:url_id>', reader.get, name='reader'),
|
path('reader/<int:url_id>', reader.get, name='reader'),
|
||||||
|
path('search/', search.search, name='search'),
|
||||||
]
|
]
|
||||||
|
@ -8,26 +8,8 @@ from django.conf import settings
|
|||||||
|
|
||||||
class URL(APIView):
|
class URL(APIView):
|
||||||
def post(self, request, format=None):
|
def post(self, request, format=None):
|
||||||
url = request.GET.get('url')
|
if request.GET.get('url'):
|
||||||
if url:
|
get_url_content_task.delay(request.GET.get('url'), request.user.id)
|
||||||
page = get_content(url)
|
|
||||||
|
|
||||||
try:
|
|
||||||
title = page['title']
|
|
||||||
except KeyError:
|
|
||||||
title = ''
|
|
||||||
|
|
||||||
content = None
|
|
||||||
if settings.READABILITY_HOST:
|
|
||||||
try:
|
|
||||||
content = page['excerpt']
|
|
||||||
except KeyError:
|
|
||||||
content = ''
|
|
||||||
|
|
||||||
url = URLModel(url=url, user=request.user,
|
|
||||||
title=title, content=content)
|
|
||||||
url.save()
|
|
||||||
get_url_content_task.delay(url.id)
|
|
||||||
return Response({'status': 'success'})
|
return Response({'status': 'success'})
|
||||||
else:
|
else:
|
||||||
return Response({'status': 'error'})
|
return Response({'status': 'error'})
|
||||||
|
@ -3,16 +3,20 @@ from django.contrib.auth.decorators import login_required
|
|||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
from later42.models.article import Article
|
||||||
from later42.models.urls import URL
|
from later42.models.urls import URL
|
||||||
|
|
||||||
|
|
||||||
def get(request):
|
def get(request):
|
||||||
|
data = {}
|
||||||
try:
|
try:
|
||||||
urls = URL.objects.filter(
|
urls = URL.objects.filter(
|
||||||
user=request.user, archived=False).order_by('-id')
|
user=request.user, archived=False).order_by('-id')
|
||||||
|
data = Article.objects.filter(url__in=urls).select_related('url')
|
||||||
except:
|
except:
|
||||||
urls = []
|
urls = []
|
||||||
context = {'urls': urls}
|
context = {'data': data}
|
||||||
return render(request, 'index.html', context)
|
return render(request, 'index.html', context)
|
||||||
|
|
||||||
|
|
||||||
@ -24,12 +28,13 @@ def archive(request, url_id=None):
|
|||||||
try:
|
try:
|
||||||
urls = URL.objects.filter(
|
urls = URL.objects.filter(
|
||||||
user=request.user, archived=True).order_by('-id')
|
user=request.user, archived=True).order_by('-id')
|
||||||
paginator = Paginator(urls, settings.URLS_PER_PAGE)
|
data = Article.objects.filter(url__in=urls).select_related('url')
|
||||||
|
paginator = Paginator(data, settings.URLS_PER_PAGE)
|
||||||
page_number = request.GET.get('page')
|
page_number = request.GET.get('page')
|
||||||
urls = paginator.get_page(page_number)
|
data = paginator.get_page(page_number)
|
||||||
except:
|
except:
|
||||||
urls = []
|
urls = []
|
||||||
context = {'urls': urls}
|
context = {'data': data}
|
||||||
return render(request, 'archive.html', context)
|
return render(request, 'archive.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,16 +6,13 @@ from django.conf import settings
|
|||||||
from later42.libs.content import get_content, sanitize_img_size
|
from later42.libs.content import get_content, sanitize_img_size
|
||||||
from later42.models.article import Article
|
from later42.models.article import Article
|
||||||
from later42.models.urls import URL
|
from later42.models.urls import URL
|
||||||
from later42.tasks import get_url_content_task
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def get(request, url_id=None):
|
def get(request, url_id=None):
|
||||||
url = URL.objects.get(
|
url = URL.objects.get(
|
||||||
user=request.user, id=url_id)
|
user=request.user, id=url_id)
|
||||||
|
|
||||||
content = {}
|
content = {}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
article = Article.objects.get(url=url)
|
article = Article.objects.get(url=url)
|
||||||
content['rich_content'] = article.content
|
content['rich_content'] = article.content
|
||||||
@ -24,6 +21,5 @@ def get(request, url_id=None):
|
|||||||
content['rich_content'] = sanitize_img_size(content['rich_content'])
|
content['rich_content'] = sanitize_img_size(content['rich_content'])
|
||||||
except:
|
except:
|
||||||
content = get_content(url.url)
|
content = get_content(url.url)
|
||||||
get_url_content_task.delay(url.id)
|
|
||||||
context = {'url': url, 'content': content}
|
context = {'url': url, 'content': content}
|
||||||
return render(request, 'reader.html', context)
|
return render(request, 'reader.html', context)
|
||||||
|
29
later42/views/search.py
Normal file
29
later42/views/search.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.core.paginator import Paginator
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
from later42.models.article import Article
|
||||||
|
from later42.models.urls import URL as URL
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def search(request):
|
||||||
|
pattern = request.POST.get('search')
|
||||||
|
context = {}
|
||||||
|
if request.method == 'GET':
|
||||||
|
return render(request, 'search.html', context)
|
||||||
|
elif request.method == 'POST':
|
||||||
|
urls = URL.objects.filter(
|
||||||
|
user=request.user).order_by('-id')
|
||||||
|
data = Article.objects.filter(
|
||||||
|
Q(title__contains=pattern) |
|
||||||
|
Q(content__contains=pattern) |
|
||||||
|
Q(short__contains=pattern) |
|
||||||
|
Q(url__url__contains=pattern), url__user_id=request.user.id).select_related('url')
|
||||||
|
paginator = Paginator(data, settings.URLS_PER_PAGE)
|
||||||
|
page_number = request.GET.get('page')
|
||||||
|
data = paginator.get_page(page_number)
|
||||||
|
context = {'data': data}
|
||||||
|
return render(request, 'search.html', context)
|
Loading…
Reference in New Issue
Block a user