Files
2026-05-28 17:51:07 +03:00

482 lines
10 KiB
Python

from django.shortcuts import render, get_object_or_404
from django.core.paginator import Paginator
from django.contrib.auth import login as auth_login, logout as auth_logout
from django.shortcuts import redirect
from django.utils.http import url_has_allowed_host_and_scheme
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.views.decorators.http import require_POST
from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
from .models import (
Question,
Tag,
Answer,
QuestionReaction,
AnswerReaction
)
from .forms import (
LoginForm,
SignupForm,
ProfileForm,
AskForm,
AnswerForm
)
from . import tasks as tasks_module
def index(request):
tag_name = request.GET.get('tag')
questions = Question.objects.all() \
.select_related('user') \
.prefetch_related('tags') \
.order_by('-created_at')
if tag_name:
questions = questions.filter(tags__name=tag_name)
paginator = Paginator(questions, 4)
page_number = request.GET.get('page', 1)
page_obj = paginator.get_page(page_number)
user_reactions = {}
if request.user.is_authenticated:
reactions = QuestionReaction.objects.filter(
user=request.user,
question__in=page_obj.object_list
)
user_reactions = {
reaction.question_id: reaction.positive
for reaction in reactions
}
hot_questions = Question.objects.order_by('-rating')[:4]
return render(request, 'core/index.html', {
'questions': page_obj.object_list,
'page_obj': page_obj,
'hot_questions': hot_questions,
'tags': Tag.objects.all(),
'tag': tag_name,
'user_reactions': user_reactions
})
def hot(request):
questions = Question.objects.all() \
.select_related('user') \
.prefetch_related('tags') \
.order_by('-rating')
paginator = Paginator(questions, 4)
page_number = request.GET.get('page', 1)
page_obj = paginator.get_page(page_number)
user_reactions = {}
if request.user.is_authenticated:
reactions = QuestionReaction.objects.filter(
user=request.user,
question__in=page_obj.object_list
)
user_reactions = {
reaction.question_id: reaction.positive
for reaction in reactions
}
return render(request, 'core/hot.html', {
'questions': page_obj.object_list,
'page_obj': page_obj,
'hot_questions': questions[:4],
'tags': Tag.objects.all(),
'user_reactions': user_reactions
})
def search(request):
query_text = request.GET.get('q', '').strip()
if not query_text:
return JsonResponse({'results': []})
vector = (
SearchVector('title', weight='A') +
SearchVector('body', weight='B')
)
query = SearchQuery(query_text, search_type='websearch')
questions = Question.objects.annotate(
rank=SearchRank(vector, query)
).filter(rank__gt=0).order_by('-rank')[:10]
results = [
{
'id': q.id,
'title': q.title,
'url': f'/question?question_id={q.id}'
}
for q in questions
]
return JsonResponse({'results': results})
def question(request):
question_id = request.GET.get('question_id')
question = get_object_or_404(
Question.objects.select_related('user')
.prefetch_related('tags', 'answers'),
id=question_id
)
if request.method == 'POST':
if not request.user.is_authenticated:
return redirect(
f'/login?next=/question?question_id={question_id}'
)
form = AnswerForm(request.POST)
if form.is_valid():
answer = form.save(
user=request.user,
question=question
)
# trigger async notifications and cache updates
try:
payload = {
'id': answer.id,
'body': answer.body,
'user': answer.user.username
}
tasks_module.publish_new_answer_centrifugo.delay(question.id, payload)
except Exception:
pass
try:
# send email to question author
recipient = []
if hasattr(question.user, 'profile'):
recipient = [question.user.profile.email]
subject = f'New answer to your question: {question.title}'
message = f'User {answer.user.username} answered your question.\n\n{answer.body}'
if recipient and recipient[0]:
tasks_module.send_new_answer_email.delay(subject, message, recipient)
except Exception:
pass
# optionally trigger cache rebuilds
try:
tasks_module.rebuild_popular_tags.delay()
tasks_module.rebuild_best_members.delay()
except Exception:
pass
return redirect(
f'/question?question_id={question.id}#answer-{answer.id}'
)
else:
form = AnswerForm()
question_reaction = None
answer_reactions = {}
if request.user.is_authenticated:
question_reaction = QuestionReaction.objects.filter(
question=question,
user=request.user
).first()
reactions = AnswerReaction.objects.filter(
user=request.user,
answer__question=question
)
answer_reactions = {
reaction.answer_id: reaction.positive
for reaction in reactions
}
return render(request, 'core/question.html', {
'question': question,
'answers': question.answers.all(),
'form': form,
'tags': Tag.objects.all(),
'question_reaction': question_reaction,
'answer_reactions': answer_reactions
})
def login(request):
next_url = request.GET.get('next', '/')
if request.method == 'POST':
form = LoginForm(request.POST)
if form.is_valid():
user = form.cleaned_data['user']
auth_login(request, user)
if not url_has_allowed_host_and_scheme(
next_url,
allowed_hosts={request.get_host()}
):
next_url = '/'
return redirect(next_url)
else:
form = LoginForm()
return render(request, 'core/login.html', {
'form': form,
'next': next_url,
'tags': Tag.objects.all()
})
def signup(request):
if request.method == 'POST':
form = SignupForm(request.POST, request.FILES)
if form.is_valid():
user = form.save()
auth_login(request, user)
return redirect('/')
else:
form = SignupForm()
return render(request, 'core/signup.html', {
'form': form,
'tags': Tag.objects.all()
})
def logout(request):
auth_logout(request)
return redirect(
request.META.get('HTTP_REFERER', '/')
)
@login_required
def profile(request):
if request.method == 'POST':
form = ProfileForm(
request.POST,
request.FILES,
user=request.user
)
if form.is_valid():
form.save()
return redirect('profile')
else:
form = ProfileForm(user=request.user)
return render(request, 'core/profile.html', {
'form': form,
'tags': Tag.objects.all()
})
@login_required
def ask(request):
if request.method == 'POST':
form = AskForm(request.POST)
if form.is_valid():
question = form.save(user=request.user)
return redirect(
f'/question?question_id={question.id}'
)
else:
form = AskForm()
return render(request, 'core/ask.html', {
'form': form,
'tags': Tag.objects.all()
})
@require_POST
@login_required
def question_react(request):
question_id = request.POST.get('question_id')
reaction_type = request.POST.get('type')
if reaction_type not in ['like', 'dislike']:
return JsonResponse({
'error': 'invalid type'
}, status=400)
question = get_object_or_404(
Question,
id=question_id
)
if QuestionReaction.objects.filter(
question=question,
user=request.user
).exists():
return JsonResponse({
'error': 'already reacted'
}, status=400)
positive = reaction_type == 'like'
QuestionReaction.objects.create(
question=question,
user=request.user,
positive=positive
)
if positive:
question.rating += 1
else:
question.rating -= 1
question.save()
return JsonResponse({
'rating': question.rating
})
@require_POST
@login_required
def answer_react(request):
answer_id = request.POST.get('answer_id')
reaction_type = request.POST.get('type')
if reaction_type not in ['like', 'dislike']:
return JsonResponse({
'error': 'invalid type'
}, status=400)
answer = get_object_or_404(
Answer,
id=answer_id
)
if AnswerReaction.objects.filter(
answer=answer,
user=request.user
).exists():
return JsonResponse({
'error': 'already reacted'
}, status=400)
positive = reaction_type == 'like'
AnswerReaction.objects.create(
answer=answer,
user=request.user,
positive=positive
)
if positive:
answer.rating += 1
else:
answer.rating -= 1
answer.save()
return JsonResponse({
'rating': answer.rating
})
@require_POST
@login_required
def mark_correct(request):
question_id = request.POST.get('question_id')
answer_id = request.POST.get('answer_id')
question = get_object_or_404(
Question,
id=question_id
)
answer = get_object_or_404(
Answer,
id=answer_id
)
if question.user != request.user:
return JsonResponse({
'error': 'forbidden'
}, status=403)
if answer.question != question:
return JsonResponse({
'error': 'wrong answer'
}, status=400)
question.correct_answer = answer
question.save()
return JsonResponse({
'success': True,
'correct_answer_id': answer.id
})