<<<<<<< HEAD
<<<<<<< HEAD
import logging
from django.utils.translation import gettext_lazy as _
from django.conf import settings
from django.contrib import auth
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth import get_user_model
from django.contrib.auth import logout
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.hashers import make_password
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from django.shortcuts import get_object_or_404
from django.shortcuts import render
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.http import url_has_allowed_host_and_scheme
from django.views import View
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import FormView, RedirectView

from djangoblog.utils import send_email, get_sha256, get_current_site, generate_code, delete_sidebar_cache
from . import utils
from .forms import RegisterForm, LoginForm, ForgetPasswordForm, ForgetPasswordCodeForm
from .models import BlogUser
=======
import logging
import os
import uuid

from django.conf import settings
from django.core.paginator import Paginator
from django.http import HttpResponse, HttpResponseForbidden
from django.shortcuts import get_object_or_404
from django.shortcuts import render
from django.templatetags.static import static
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.detail import DetailView
from django.views.generic.list import ListView
from haystack.views import SearchView

from blog.models import Article, Category, LinkShowType, Links, Tag
from comments.forms import CommentForm
from djangoblog.plugin_manage import hooks
from djangoblog.plugin_manage.hook_constants import ARTICLE_CONTENT_HOOK_NAME
from djangoblog.utils import cache, get_blog_setting, get_sha256
>>>>>>> hyt_branch

logger = logging.getLogger(__name__)


<<<<<<< HEAD
# Create your views here.

class RegisterView(FormView):
    form_class = RegisterForm
    template_name = 'account/registration_form.html'

    @method_decorator(csrf_protect)
    def dispatch(self, *args, **kwargs):
        return super(RegisterView, self).dispatch(*args, **kwargs)

    def form_valid(self, form):
        if form.is_valid():
            user = form.save(False)
            user.is_active = False
            user.source = 'Register'
            user.save(True)
            site = get_current_site().domain
            sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))

            if settings.DEBUG:
                site = '127.0.0.1:8000'
            path = reverse('account:result')
            url = "http://{site}{path}?type=validation&id={id}&sign={sign}".format(
                site=site, path=path, id=user.id, sign=sign)

            content = """
                            <p>请点击下面链接验证您的邮箱</p>

                            <a href="{url}" rel="bookmark">{url}</a>

                            再次感谢您！
                            <br />
                            如果上面链接无法打开，请将此链接复制至浏览器。
                            {url}
                            """.format(url=url)
            send_email(
                emailto=[
                    user.email,
                ],
                title='验证您的电子邮箱',
                content=content)

            url = reverse('accounts:result') + \
                  '?type=register&id=' + str(user.id)
            return HttpResponseRedirect(url)
        else:
            return self.render_to_response({
                'form': form
            })


class LogoutView(RedirectView):
    url = '/login/'

    @method_decorator(never_cache)
    def dispatch(self, request, *args, **kwargs):
        return super(LogoutView, self).dispatch(request, *args, **kwargs)

    def get(self, request, *args, **kwargs):
        logout(request)
        delete_sidebar_cache()
        return super(LogoutView, self).get(request, *args, **kwargs)


class LoginView(FormView):
    form_class = LoginForm
    template_name = 'account/login.html'
    success_url = '/'
    redirect_field_name = REDIRECT_FIELD_NAME
    login_ttl = 2626560  # 一个月的时间

    @method_decorator(sensitive_post_parameters('password'))
    @method_decorator(csrf_protect)
    @method_decorator(never_cache)
    def dispatch(self, request, *args, **kwargs):

        return super(LoginView, self).dispatch(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        redirect_to = self.request.GET.get(self.redirect_field_name)
        if redirect_to is None:
            redirect_to = '/'
        kwargs['redirect_to'] = redirect_to

        return super(LoginView, self).get_context_data(**kwargs)

    def form_valid(self, form):
        form = AuthenticationForm(data=self.request.POST, request=self.request)

        if form.is_valid():
            delete_sidebar_cache()
            logger.info(self.redirect_field_name)

            auth.login(self.request, form.get_user())
            if self.request.POST.get("remember"):
                self.request.session.set_expiry(self.login_ttl)
            return super(LoginView, self).form_valid(form)
            # return HttpResponseRedirect('/')
        else:
            return self.render_to_response({
                'form': form
            })

    def get_success_url(self):

        redirect_to = self.request.POST.get(self.redirect_field_name)
        if not url_has_allowed_host_and_scheme(
                url=redirect_to, allowed_hosts=[
                    self.request.get_host()]):
            redirect_to = self.success_url
        return redirect_to


def account_result(request):
    type = request.GET.get('type')
    id = request.GET.get('id')

    user = get_object_or_404(get_user_model(), id=id)
    logger.info(type)
    if user.is_active:
        return HttpResponseRedirect('/')
    if type and type in ['register', 'validation']:
        if type == 'register':
            content = '''
    恭喜您注册成功，一封验证邮件已经发送到您的邮箱，请验证您的邮箱后登录本站。
    '''
            title = '注册成功'
        else:
            c_sign = get_sha256(get_sha256(settings.SECRET_KEY + str(user.id)))
            sign = request.GET.get('sign')
            if sign != c_sign:
                return HttpResponseForbidden()
            user.is_active = True
            user.save()
            content = '''
            恭喜您已经成功的完成邮箱验证，您现在可以使用您的账号来登录本站。
            '''
            title = '验证成功'
        return render(request, 'account/result.html', {
            'title': title,
            'content': content
        })
    else:
        return HttpResponseRedirect('/')


class ForgetPasswordView(FormView):
    form_class = ForgetPasswordForm
    template_name = 'account/forget_password.html'

    def form_valid(self, form):
        if form.is_valid():
            blog_user = BlogUser.objects.filter(email=form.cleaned_data.get("email")).get()
            blog_user.password = make_password(form.cleaned_data["new_password2"])
            blog_user.save()
            return HttpResponseRedirect('/login/')
        else:
            return self.render_to_response({'form': form})


class ForgetPasswordEmailCode(View):

    def post(self, request: HttpRequest):
        form = ForgetPasswordCodeForm(request.POST)
        if not form.is_valid():
            return HttpResponse("错误的邮箱")
        to_email = form.cleaned_data["email"]

        code = generate_code()
        utils.send_verify_email(to_email, code)
        utils.set_code(to_email, code)

        return HttpResponse("ok")
=======
from django.core.exceptions import ValidationError

from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
# 导入方法装饰器工具，用于装饰类中的方法
from django.utils.decorators import method_decorator
# 导入CSRF保护装饰器，防止跨站请求伪造
from django.views.decorators.csrf import csrf_protect
# 导入表单视图基类，用于处理表单提交逻辑
from django.views.generic.edit import FormView

# 导入用户模型，用于获取评论作者信息
from accounts.models import BlogUser
# 导入文章模型，用于关联评论所属文章
from blog.models import Article
# 导入评论表单类，用于处理评论提交数据
from .forms import CommentForm
# 导入评论模型，用于创建和保存评论
from .models import Comment


# 定义评论提交视图类，继承自FormView（表单处理基类）
class CommentPostView(FormView):
    form_class = CommentForm  # 指定使用的表单类为CommentForm
    template_name = 'blog/article_detail.html'  # 指定表单验证失败时渲染的模板

    # 使用CSRF保护装饰器装饰dispatch方法，确保表单提交安全
    @method_decorator(csrf_protect)
    def dispatch(self, *args, **kwargs):
        # 调用父类的dispatch方法，处理请求分发
        return super(CommentPostView, self).dispatch(*args, **kwargs)

    # 处理GET请求：重定向到文章详情页的评论区
    def get(self, request, *args, **kwargs):
        # 从URL参数中获取文章ID
        article_id = self.kwargs['article_id']
        # 获取对应的文章对象，不存在则返回404
        article = get_object_or_404(Article, pk=article_id)
        # 获取文章详情页的URL
        url = article.get_absolute_url()
        # 重定向到文章详情页的评论区（通过锚点#comments定位）
        return HttpResponseRedirect(url + "#comments")

    # 处理表单验证失败的逻辑
    def form_invalid(self, form):
        # 从URL参数中获取文章ID
        article_id = self.kwargs['article_id']
        # 获取对应的文章对象
        article = get_object_or_404(Article, pk=article_id)

        # 渲染文章详情页模板，传递错误的表单和文章对象（用于显示错误信息）
        return self.render_to_response({
            'form': form,
            'article': article
        })

    # 处理表单验证成功后的逻辑
    def form_valid(self, form):
        """提交的数据验证合法后的逻辑"""
        # 获取当前登录用户
        user = self.request.user
        # 根据用户ID获取对应的用户对象（评论作者）
        author = BlogUser.objects.get(pk=user.pk)
        # 从URL参数中获取文章ID
        article_id = self.kwargs['article_id']
        # 获取对应的文章对象
        article = get_object_or_404(Article, pk=article_id)

        # 检查文章是否允许评论：若文章评论状态为关闭或文章状态为草稿，则抛出验证错误
        if article.comment_status == 'c' or article.status == 'c':
            raise ValidationError("该文章评论已关闭.")
        # 保存表单数据但不提交到数据库（获取评论对象）
        comment = form.save(False)
        # 关联评论到对应的文章
        comment.article = article
        # 导入工具函数，获取博客设置
        from djangoblog.utils import get_blog_setting
        settings = get_blog_setting()
        # 若博客设置为评论无需审核，则直接启用评论
        if not settings.comment_need_review:
            comment.is_enable = True
        # 设置评论的作者
        comment.author = author

        # 处理回复功能：若存在父评论ID，则关联到父评论
        if form.cleaned_data['parent_comment_id']:
            # 根据父评论ID获取父评论对象
            parent_comment = Comment.objects.get(
                pk=form.cleaned_data['parent_comment_id'])
            # 设置当前评论的父评论
            comment.parent_comment = parent_comment

        # 保存评论到数据库（执行真正的保存操作）
        comment.save(True)
        # 重定向到文章详情页的当前评论位置（通过锚点#div-comment-{评论ID}定位）
        return HttpResponseRedirect(
            "%s#div-comment-%d" %
            (article.get_absolute_url(), comment.pk))
>>>>>>> zh_branch
=======
class ArticleListView(ListView):
    """
    文章列表基类视图
    提供通用的文章列表功能和缓存机制
    所有文章列表视图都应该继承此类
    """
    # template_name属性用于指定使用哪个模板进行渲染
    template_name = 'blog/article_index.html'

    # context_object_name属性用于给上下文变量取名（在模板中使用该名字）
    context_object_name = 'article_list'

    # 页面类型，分类目录或标签列表等
    page_type = ''
    paginate_by = settings.PAGINATE_BY  # 每页显示的文章数量
    page_kwarg = 'page'  # URL中页码参数的名称
    link_type = LinkShowType.L  # 友情链接显示类型

    def get_view_cache_key(self):
        """获取视图缓存键 - 需要子类实现"""
        return self.request.get['pages']

    @property
    def page_number(self):
        """获取当前页码"""
        page_kwarg = self.page_kwarg
        page = self.kwargs.get(
            page_kwarg) or self.request.GET.get(page_kwarg) or 1
        return page

    def get_queryset_cache_key(self):
        """
        获取查询集缓存键
        子类必须重写此方法
        """
        raise NotImplementedError()

    def get_queryset_data(self):
        """
        获取查询集数据
        子类必须重写此方法
        """
        raise NotImplementedError()

    def get_queryset_from_cache(self, cache_key):
        """
        从缓存获取查询集数据
        Args:
            cache_key: 缓存键
        Returns:
            QuerySet: 文章查询集
        """
        value = cache.get(cache_key)
        if value:
            logger.info('get view cache.key:{key}'.format(key=cache_key))
            return value
        else:
            article_list = self.get_queryset_data()
            cache.set(cache_key, article_list)
            logger.info('set view cache.key:{key}'.format(key=cache_key))
            return article_list

    def get_queryset(self):
        """
        获取查询集 - 从缓存获取数据
        Returns:
            QuerySet: 文章查询集
        """
        key = self.get_queryset_cache_key()
        value = self.get_queryset_from_cache(key)
        return value

    def get_context_data(self, **kwargs):
        """添加上下文数据"""
        kwargs['linktype'] = self.link_type
        return super(ArticleListView, self).get_context_data(**kwargs)


class IndexView(ArticleListView):
    """
    首页视图
    显示最新的文章列表
    """
    # 友情链接类型 - 首页显示
    link_type = LinkShowType.I

    def get_queryset_data(self):
        """获取首页文章数据 - 只获取已发布的普通文章"""
        article_list = Article.objects.filter(type='a', status='p')
        return article_list

    def get_queryset_cache_key(self):
        """获取首页缓存键 - 基于页码"""
        cache_key = 'index_{page}'.format(page=self.page_number)
        return cache_key


class ArticleDetailView(DetailView):
    """
    文章详情页面视图
    显示单篇文章的详细内容和评论
    """
    template_name = 'blog/article_detail.html'
    model = Article  # 关联的模型
    pk_url_kwarg = 'article_id'  # URL中主键参数的名称
    context_object_name = "article"  # 模板中使用的变量名

    def get_context_data(self, **kwargs):
        """添加上下文数据 - 文章详情和评论信息"""
        # 创建评论表单
        comment_form = CommentForm()

        # 获取文章评论列表
        article_comments = self.object.comment_list()
        # 获取顶级评论（没有父评论的评论）
        parent_comments = article_comments.filter(parent_comment=None)

        # 获取博客设置
        blog_setting = get_blog_setting()

        # 对评论进行分页
        paginator = Paginator(parent_comments, blog_setting.article_comment_count)
        page = self.request.GET.get('comment_page', '1')

        # 验证页码
        if not page.isnumeric():
            page = 1
        else:
            page = int(page)
            if page < 1:
                page = 1
            if page > paginator.num_pages:
                page = paginator.num_pages

        # 获取当前页的评论
        p_comments = paginator.page(page)

        # 计算下一页和上一页
        next_page = p_comments.next_page_number() if p_comments.has_next() else None
        prev_page = p_comments.previous_page_number() if p_comments.has_previous() else None

        # 构建评论分页URL
        if next_page:
            kwargs[
                'comment_next_page_url'] = self.object.get_absolute_url() + f'?comment_page={next_page}#commentlist-container'
        if prev_page:
            kwargs[
                'comment_prev_page_url'] = self.object.get_absolute_url() + f'?comment_page={prev_page}#commentlist-container'

        # 添加上下文数据
        kwargs['form'] = comment_form
        kwargs['article_comments'] = article_comments
        kwargs['p_comments'] = p_comments
        kwargs['comment_count'] = len(article_comments) if article_comments else 0

        # 添加上下篇文章信息
        kwargs['next_article'] = self.object.next_article
        kwargs['prev_article'] = self.object.prev_article

        # 调用父类方法获取基础上下文
        context = super(ArticleDetailView, self).get_context_data(**kwargs)
        article = self.object

        # Action Hook, 通知插件"文章详情已获取"
        hooks.run_action('after_article_body_get', article=article, request=self.request)

        # Filter Hook, 允许插件修改文章正文
        article.body = hooks.apply_filters(ARTICLE_CONTENT_HOOK_NAME, article.body, article=article,
                                           request=self.request)

        return context


class CategoryDetailView(ArticleListView):
    """
    分类目录列表视图
    显示指定分类下的所有文章（包括子分类）
    """
    page_type = "分类目录归档"

    def get_queryset_data(self):
        """获取分类文章数据 - 包括所有子分类的文章"""
        slug = self.kwargs['category_name']
        category = get_object_or_404(Category, slug=slug)

        categoryname = category.name
        self.categoryname = categoryname

        # 获取所有子分类的名称
        categorynames = list(
            map(lambda c: c.name, category.get_sub_categorys()))

        # 获取这些分类下的所有已发布文章
        article_list = Article.objects.filter(
            category__name__in=categorynames, status='p')
        return article_list

    def get_queryset_cache_key(self):
        """获取分类页面缓存键 - 基于分类名称和页码"""
        slug = self.kwargs['category_name']
        category = get_object_or_404(Category, slug=slug)
        categoryname = category.name
        self.categoryname = categoryname
        cache_key = 'category_list_{categoryname}_{page}'.format(
            categoryname=categoryname, page=self.page_number)
        return cache_key

    def get_context_data(self, **kwargs):
        """添加上下文数据"""
        categoryname = self.categoryname
        try:
            categoryname = categoryname.split('/')[-1]  # 处理多层分类名称
        except BaseException:
            pass
        kwargs['page_type'] = CategoryDetailView.page_type
        kwargs['tag_name'] = categoryname
        return super(CategoryDetailView, self).get_context_data(**kwargs)


class AuthorDetailView(ArticleListView):
    """
    作者详情页视图
    显示指定作者的所有文章
    """
    page_type = '作者文章归档'

    def get_queryset_cache_key(self):
        """获取作者页面缓存键 - 基于作者名称和页码"""
        from uuslug import slugify
        author_name = slugify(self.kwargs['author_name'])  # 使用slugify处理作者名
        cache_key = 'author_{author_name}_{page}'.format(
            author_name=author_name, page=self.page_number)
        return cache_key

    def get_queryset_data(self):
        """获取作者文章数据 - 指定作者的所有已发布文章"""
        author_name = self.kwargs['author_name']
        article_list = Article.objects.filter(
            author__username=author_name, type='a', status='p')
        return article_list

    def get_context_data(self, **kwargs):
        """添加上下文数据"""
        author_name = self.kwargs['author_name']
        kwargs['page_type'] = AuthorDetailView.page_type
        kwargs['tag_name'] = author_name
        return super(AuthorDetailView, self).get_context_data(**kwargs)


class TagDetailView(ArticleListView):
    """
    标签列表页面视图
    显示指定标签下的所有文章
    """
    page_type = '分类标签归档'

    def get_queryset_data(self):
        """获取标签文章数据 - 指定标签的所有已发布文章"""
        slug = self.kwargs['tag_name']
        tag = get_object_or_404(Tag, slug=slug)
        tag_name = tag.name
        self.name = tag_name
        article_list = Article.objects.filter(
            tags__name=tag_name, type='a', status='p')
        return article_list

    def get_queryset_cache_key(self):
        """获取标签页面缓存键 - 基于标签名称和页码"""
        slug = self.kwargs['tag_name']
        tag = get_object_or_404(Tag, slug=slug)
        tag_name = tag.name
        self.name = tag_name
        cache_key = 'tag_{tag_name}_{page}'.format(
            tag_name=tag_name, page=self.page_number)
        return cache_key

    def get_context_data(self, **kwargs):
        """添加上下文数据"""
        tag_name = self.name
        kwargs['page_type'] = TagDetailView.page_type
        kwargs['tag_name'] = tag_name
        return super(TagDetailView, self).get_context_data(**kwargs)


class ArchivesView(ArticleListView):
    """
    文章归档页面视图
    按时间顺序显示所有文章（不分页）
    """
    page_type = '文章归档'
    paginate_by = None  # 不分页
    page_kwarg = None
    template_name = 'blog/article_archives.html'  # 使用专门的归档模板

    def get_queryset_data(self):
        """获取归档数据 - 所有已发布文章"""
        return Article.objects.filter(status='p').all()

    def get_queryset_cache_key(self):
        """获取归档页面缓存键 - 固定键名"""
        cache_key = 'archives'
        return cache_key


class LinkListView(ListView):
    """
    友情链接列表视图
    显示所有启用的友情链接
    """
    model = Links  # 关联的模型
    template_name = 'blog/links_list.html'  # 友情链接模板

    def get_queryset(self):
        """获取启用的友情链接"""
        return Links.objects.filter(is_enable=True)


class EsSearchView(SearchView):
    """
    Elasticsearch搜索视图
    扩展Haystack的搜索功能
    """

    def get_context(self):
        """获取搜索上下文数据"""
        paginator, page = self.build_page()  # 构建分页
        context = {
            "query": self.query,  # 搜索关键词
            "form": self.form,  # 搜索表单
            "page": page,  # 当前页
            "paginator": paginator,  # 分页器
            "suggestion": None,  # 搜索建议
        }

        # 添加拼写建议
        if hasattr(self.results, "query") and self.results.query.backend.include_spelling:
            context["suggestion"] = self.results.query.get_spelling_suggestion()
        context.update(self.extra_context())

        return context


@csrf_exempt  # 免除CSRF验证，用于文件上传
def fileupload(request):
    """
    文件上传视图
    提供图床功能，支持图片和文件上传
    Args:
        request: HTTP请求对象
    Returns:
        HttpResponse: 上传结果
    """
    if request.method == 'POST':
        # 验证签名，确保上传请求合法
        sign = request.GET.get('sign', None)
        if not sign:
            return HttpResponseForbidden()
        if not sign == get_sha256(get_sha256(settings.SECRET_KEY)):
            return HttpResponseForbidden()

        response = []
        # 处理所有上传的文件
        for filename in request.FILES:
            # 按日期创建目录结构
            timestr = timezone.now().strftime('%Y/%m/%d')
            imgextensions = ['jpg', 'png', 'jpeg', 'bmp']  # 图片文件扩展名
            fname = u''.join(str(filename))

            # 判断是否为图片文件
            isimage = len([i for i in imgextensions if fname.find(i) >= 0]) > 0

            # 创建存储目录
            base_dir = os.path.join(settings.STATICFILES, "files" if not isimage else "image", timestr)
            if not os.path.exists(base_dir):
                os.makedirs(base_dir)

            # 生成唯一文件名
            savepath = os.path.normpath(os.path.join(base_dir, f"{uuid.uuid4().hex}{os.path.splitext(filename)[-1]}"))

            # 安全检查：确保文件保存在指定目录内
            if not savepath.startswith(base_dir):
                return HttpResponse("only for post")

            # 保存文件
            with open(savepath, 'wb+') as wfile:
                for chunk in request.FILES[filename].chunks():
                    wfile.write(chunk)

            # 如果是图片，进行压缩优化
            if isimage:
                from PIL import Image
                image = Image.open(savepath)
                image.save(savepath, quality=20, optimize=True)  # 压缩质量20%

            # 生成静态文件URL
            url = static(savepath)
            response.append(url)
        return HttpResponse(response)

    else:
        return HttpResponse("only for post")  # 只支持POST请求


def page_not_found_view(
        request,
        exception,
        template_name='blog/error_page.html'):
    """
    404页面未找到视图
    Args:
        request: 请求对象
        exception: 异常信息
        template_name: 模板名称
    Returns:
        HttpResponse: 404错误页面
    """
    if exception:
        logger.error(exception)  # 记录异常日志
    url = request.get_full_path()
    return render(request,
                  template_name,
                  {'message': _('Sorry, the page you requested is not found, please click the home page to see other?'),
                   'statuscode': '404'},
                  status=404)


def server_error_view(request, template_name='blog/error_page.html'):
    """
    500服务器错误视图
    Args:
        request: 请求对象
        template_name: 模板名称
    Returns:
        HttpResponse: 500错误页面
    """
    return render(request,
                  template_name,
                  {'message': _('Sorry, the server is busy, please click the home page to see other?'),
                   'statuscode': '500'},
                  status=500)


def permission_denied_view(
        request,
        exception,
        template_name='blog/error_page.html'):
    """
    403权限拒绝视图
    Args:
        request: 请求对象
        exception: 异常信息
        template_name: 模板名称
    Returns:
        HttpResponse: 403错误页面
    """
    if exception:
        logger.error(exception)  # 记录异常日志
    return render(
        request, template_name, {
            'message': _('Sorry, you do not have permission to access this page?'),
            'statuscode': '403'}, status=403)


def clean_cache_view(request):
    """
    清理缓存视图
    用于手动清理系统缓存
    Args:
        request: 请求对象
    Returns:
        HttpResponse: 清理结果
    """
    cache.clear()
    return HttpResponse('ok')
>>>>>>> hyt_branch
