Exemplo n.º 1
0
def archive_year(request, year, template_name="blog/archive_year.html", extra_context=None):
    """
    Year archive page view, list all article published the given year.
    :param request: The incoming request.
    :param year: The desired archive's year.
    :param template_name: The template name to be used.
    :param extra_context: Any extra context for the template.
    :return: TemplateResponse
    """

    # Year/month cleaning
    year = int(year)
    if year == 0:
        raise Http404()

    # Retrieve all published articles for the given year
    published_articles = (
        Article.objects.published()
        .filter(pub_date__year=year)
        .select_related("author")
        .prefetch_related("tags", "categories")
    )

    # Articles list pagination
    paginator, page = paginate(published_articles, request, NB_ARTICLES_PER_PAGE)

    # Render the template
    context = {"title": _("Articles for year %d") % year, "year": year}
    update_context_for_pagination(context, "articles", request, paginator, page)
    if extra_context is not None:
        context.update(extra_context)

    return TemplateResponse(request, template_name, context)
Exemplo n.º 2
0
def image_attachment_list(request,
                          template_name='imageattachments/image_attachment_list.html',
                          extra_context=None):
    """
    List all recently published image attachments.
    :param request: The incoming request.
    :param template_name: The template name to be used.
    :param extra_context: Any extra context for the template.
    :return: TemplateResponse
    """

    # Retrieve all published image attachments
    published_images = ImageAttachment.objects.published_public().select_related('license')

    # Images list pagination
    paginator, page = paginate(published_images, request, NB_IMG_ATTACHMENTS_PER_PAGE)

    # Render the template
    context = {
        'title': _('Image attachments'),
    }
    update_context_for_pagination(context, 'images', request, paginator, page)
    if extra_context is not None:
        context.update(extra_context)

    return TemplateResponse(request, template_name, context)
Exemplo n.º 3
0
def category_detail(request, hierarchy, template_name="blog/category_detail.html", extra_context=None):
    """
    Detail view for a specific category.
    :param hierarchy: The desired category's slug(s) hierarchy.
    :param request: The incoming request.
    :param template_name: The template name to be used.
    :param extra_context: Any extra context for the template.
    :return: TemplateResponse
    """

    # Get the desired category
    manager = ArticleCategory.objects.select_related("parent")
    category_obj = get_object_or_404(manager, slug_hierarchy=hierarchy)

    # Related articles list pagination
    paginator, page = paginate(
        category_obj.articles.published().select_related("author").prefetch_related("tags", "categories"),
        request,
        NB_ARTICLES_PER_PAGE,
    )

    # Render the template
    context = {"title": _("Category %s") % category_obj.name, "category": category_obj}
    update_context_for_pagination(context, "related_articles", request, paginator, page)
    if extra_context is not None:
        context.update(extra_context)

    return TemplateResponse(request, template_name, context)
Exemplo n.º 4
0
def license_detail(request, slug, template_name="blog/license_detail.html", extra_context=None):
    """
    Detail view for a specific license.
    :param slug: The desired license's slug.
    :param request: The incoming request.
    :param template_name: The template name to be used.
    :param extra_context: Any extra context for the template.
    :return: TemplateResponse
    """

    # Get the desired license
    license_obj = get_object_or_404(License, slug=slug)

    # Related articles list pagination
    paginator, page = paginate(
        license_obj.articles.published().select_related("author").prefetch_related("tags", "categories"),
        request,
        NB_ARTICLES_PER_PAGE,
    )

    # Render the template
    context = {"title": _("License %s") % license_obj.name, "license": license_obj}
    update_context_for_pagination(context, "related_articles", request, paginator, page)
    if extra_context is not None:
        context.update(extra_context)

    return TemplateResponse(request, template_name, context)
Exemplo n.º 5
0
def accounts_list(request,
                  template_name='accounts/accounts_list.html',
                  extra_context=None):
    """
    Accounts page view, display all registered user accounts as a paginated list.
    :param request: The current request.
    :param template_name: The template name to be used.
    :param extra_context: Any extra context for the template.
    :return: TemplateResponse
    """

    # Accounts listing
    user_account_list = UserProfile.objects.get_active_users_accounts().select_related('user')

    # Accounts list pagination
    paginator, page = paginate(user_account_list, request, NB_ACCOUNTS_PER_PAGE)

    # Render the template
    context = {
        'title': _('Members list'),
    }
    update_context_for_pagination(context, 'accounts', request, paginator, page)
    if extra_context is not None:
        context.update(extra_context)

    return TemplateResponse(request, template_name, context)
Exemplo n.º 6
0
def snippet_list(request,
                 template_name='snippets/snippet_list.html',
                 extra_context=None):
    """
    Code snippets list view.
    :param request: The incoming request.
    :param template_name: The template name to be used.
    :param extra_context: Any extra context for the template.
    :return: TemplateResponse
    """

    # Retrieve all published code snippets
    published_snippets = CodeSnippet.objects.public_snippets().select_related('author')

    # Code snippets list pagination
    paginator, page = paginate(published_snippets, request, NB_SNIPPETS_PER_PAGE)

    # Render the template
    context = {
        'title': _('Code snippets list'),
    }
    update_context_for_pagination(context, 'snippets', request, paginator, page)
    if extra_context is not None:
        context.update(extra_context)

    return TemplateResponse(request, template_name, context)
Exemplo n.º 7
0
def my_posts_list(request,
                  template_name='forum/forum_myposts_list.html',
                  extra_context=None):
    """
    Current user's posts list.
    :param request: The current request.
    :param template_name: The template name to be used.
    :param extra_context: Any extra context for the template.
    :return: TemplateResponse
    """

    # Get the post list for the current user
    post_list = request.user.forum_posts.published() \
        .select_related('author__user_profile', 'last_modification_by').prefetch_related('attachments')

    # Post list pagination
    paginator, page = paginate(post_list, request, NB_FORUM_POST_PER_PAGE)

    # Render the template
    context = {
        'title': _('My forum posts list'),
    }
    update_context_for_pagination(context, 'posts', request, paginator, page)
    if extra_context is not None:
        context.update(extra_context)

    return TemplateResponse(request, template_name, context)
Exemplo n.º 8
0
def my_forums_subscription_list(request,
                                template_name='forum/forum_myforumssubscription_list.html',
                                extra_context=None):
    """
    Current user's forums subscription list.
    :param request: The current request.
    :param template_name: The template name to be used.
    :param extra_context: Any extra context for the template.
    :return: TemplateResponse
    """

    # Get all subscriptions
    subscriptions = ForumSubscription.objects.filter(user=request.user, active=True).select_related('forum')

    # Forum subscriptions list pagination
    paginator, page = paginate(subscriptions, request, NB_FORUM_THREAD_PER_PAGE)

    # Render the template
    context = {
        'title': _('My forum subscriptions list'),
    }
    update_context_for_pagination(context, 'forum_subscriptions', request, paginator, page)
    if extra_context is not None:
        context.update(extra_context)

    return TemplateResponse(request, template_name, context)
Exemplo n.º 9
0
def msg_trashbox(request,
                 template_name='privatemsg/trashbox.html',
                 extra_context=None):
    """
    Display the trashbox for the current user.
    :param request: The current request.
    :param template_name: The template name to be used.
    :param extra_context: Any extra context for the template.
    :return: TemplateResponse
    """

    # Get the messages list for the current user
    messages = PrivateMessage.objects.trash_for(request.user).select_related('parent_msg', 'sender', 'recipient')

    # Messages list pagination
    paginator, page = paginate(messages, request, NB_PRIVATE_MSG_PER_PAGE)

    # Render the template
    context = {
        'title': _('Private messages trash'),
        'deletion_timeout_days': DELETED_MSG_DELETION_TIMEOUT_DAYS
    }
    update_context_for_pagination(context, 'private_messages', request, paginator, page)
    if extra_context is not None:
        context.update(extra_context)

    return TemplateResponse(request, template_name, context)
Exemplo n.º 10
0
def my_threads_list(request,
                    template_name='forum/forum_mythreads_list.html',
                    extra_context=None):
    """
    Current user's threads list.
    :param request: The current request.
    :param template_name: The template name to be used.
    :param extra_context: Any extra context for the template.
    :return: TemplateResponse
    """

    # Get the thread list for the current user
    thread_list = ForumThread.objects.published().filter(first_post__author=request.user) \
        .select_related('first_post__author', 'last_post__author')

    # Thread list pagination
    paginator, page = paginate(thread_list, request, NB_FORUM_THREAD_PER_PAGE)

    # Render the template
    context = {
        'title': _('My forum threads list'),
    }
    update_context_for_pagination(context, 'threads', request, paginator, page)
    if extra_context is not None:
        context.update(extra_context)

    return TemplateResponse(request, template_name, context)
Exemplo n.º 11
0
def msg_inbox(request, filterby='all',
              template_name='privatemsg/inbox.html',
              extra_context=None):
    """
    Display the inbox for the current user.
    :param request: The current request.
    :param filterby: The selected filtering option.
    :param template_name: The template name to be used.
    :param extra_context: Any extra context for the template.
    :return: TemplateResponse
    """

    # Get the messages list for the current user
    messages = PrivateMessage.objects.inbox_for(request.user).select_related('parent_msg', 'sender')

    # Filter the list
    if filterby == 'read':
        messages = messages.filter(read_at__isnull=False)
    elif filterby == 'unread':
        messages = messages.filter(read_at__isnull=True)

    # Messages list pagination
    paginator, page = paginate(messages, request, NB_PRIVATE_MSG_PER_PAGE)

    # Render the template
    context = {
        'title': _('Private messages inbox'),
        'filter_by': filterby
    }
    update_context_for_pagination(context, 'private_messages', request, paginator, page)
    if extra_context is not None:
        context.update(extra_context)

    return TemplateResponse(request, template_name, context)
Exemplo n.º 12
0
def my_threads_subscription_list(request,
                                 template_name='forum/forum_mythreadssubscription_list.html',
                                 extra_context=None):
    """
    Current user's threads subscription list.
    :param request: The current request.
    :param template_name: The template name to be used.
    :param extra_context: Any extra context for the template.
    :return: TemplateResponse
    """

    # Get all subscriptions
    subscriptions = ForumThreadSubscription.objects.filter(user=request.user,
                                                           thread__deleted_at__isnull=True, active=True) \
        .select_related('thread__first_post__author', 'thread__last_post__author')

    # Thread subscriptions list pagination
    paginator, page = paginate(subscriptions, request, NB_FORUM_THREAD_PER_PAGE)

    # Render the template
    context = {
        'title': _('My forum thread subscriptions list'),
    }
    update_context_for_pagination(context, 'thread_subscriptions', request, paginator, page)
    if extra_context is not None:
        context.update(extra_context)

    return TemplateResponse(request, template_name, context)
Exemplo n.º 13
0
def my_ticket_subscription_list(request,
                                template_name='bugtracker/issueticket_mysubscriptionslist.html',
                                extra_context=None):
    """
    List of all issue tickets subscribed by the user, paginated view.
    Can be sorted using the publication date or modification date with ``?sortby=pubdate`` or ``?sortby=moddate``.
    Can change sort orientation with ``?sortfrom=recent`` (recent to old) or ``?sortfrom=old`` (old to recent).
    Can filter status, priority and difficulty using ``?status=XXX``, ``?priority=XXX`` or/and ``?difficulty=XXX``
    :param request: The incoming request.
    :param template_name: The template to use for the view.
    :param extra_context: Any extra context dict.
    """
    current_user = request.user
    issue_subscriptions = IssueTicketSubscription.objects.select_related('issue') \
        .filter(user=current_user, active=True)

    # Issues list pagination
    paginator, page = paginate(issue_subscriptions, request, NB_ISSUES_PER_PAGE)

    # Template rendering
    context = {
        'title': _('My tickets subscription list'),
        }
    update_context_for_pagination(context, 'subscriptions', request, paginator, page)
    if extra_context is not None:
        context.update(extra_context)

    return TemplateResponse(request, template_name, context)
Exemplo n.º 14
0
def license_list(request,
                 template_name='licenses/license_list.html',
                 extra_context=None):
    """
    List view of all licenses.
    :param request: The incoming request.
    :param template_name: The template name to be used.
    :param extra_context: Any extra context for the template.
    :return: TemplateResponse
    """

    # Retrieve all licenses
    queryset = License.objects.all()

    # Licenses list pagination
    paginator, page = paginate(queryset, request, NB_LICENSES_PER_PAGE)

    # Render the template
    context = {
        'title': _('Licenses list'),
    }
    update_context_for_pagination(context, 'licenses', request, paginator, page)
    if extra_context is not None:
        context.update(extra_context)

    return TemplateResponse(request, template_name, context)
Exemplo n.º 15
0
def blocked_user_list(request,
                      template_name='privatemsg/blocked_user_list.html',
                      extra_context=None):
    """
    Display all blocked user for the current user.
    :param request: The current request.
    :param template_name: The template name to be used.
    :param extra_context: Any extra context for the template.
    :return: TemplateResponse
    """

    # Get all blocked user list for the current user
    blocked_user_list = BlockedUser.objects.blocked_users_for(request.user).select_related('blocked_user')

    # User list pagination
    paginator, page = paginate(blocked_user_list, request, NB_BLOCKED_USERS_PER_PAGE)

    # Render the template
    context = {
        'title': _('Blocked users list'),
    }
    update_context_for_pagination(context, 'blocked_users', request, paginator, page)
    if extra_context is not None:
        context.update(extra_context)

    return TemplateResponse(request, template_name, context)
Exemplo n.º 16
0
def _generic_tickets_list(queryset, request, template_name, context):
    """
    List of all issue tickets from the given queryset, paginated view.
    Can be sorted using the publication date or modification date with ``?sortby=pubdate`` or ``?sortby=moddate``.
    Can change sort orientation with ``?sortfrom=recent`` (recent to old) or ``?sortfrom=old`` (old to recent).
    Can filter status, priority and difficulty using ``?status=XXX``, ``?priority=XXX`` or/and ``?difficulty=XXX``
    :param queryset: The queryset to source data from.
    :param request: The incoming request.
    :param template_name: The template to use for the view.
    :param context: Any extra context dict.
    """

    # Issues sorting
    sort_by = request.GET.get('sortby', 'pubdate')
    sort_from = request.GET.get('sortfrom', 'recent')
    if sort_by == 'moddate':
        if sort_from == 'recent':
            order_by = '-last_modification_date'
        else:
            order_by = 'last_modification_date'
    else:
        if sort_from == 'recent':
            order_by = '-submission_date'
        else:
            order_by = 'submission_date'
    issues_list = queryset.order_by(order_by)

    # Issues filtering
    filter_status = request.GET.get('status', None)
    if filter_status:
        issues_list = issues_list.filter(status=filter_status)
    filter_priority = request.GET.get('priority', None)
    if filter_priority:
        issues_list = issues_list.filter(priority=filter_priority)
    filter_difficulty = request.GET.get('difficulty', None)
    if filter_difficulty:
        issues_list = issues_list.filter(status=filter_difficulty)

    # Issues list pagination
    paginator, page = paginate(issues_list.select_related('submitter'), request, NB_ISSUES_PER_PAGE)

    # Template rendering
    sort_context = {
        'sort_sortby': sort_by,
        'sort_sortfrom': sort_from,
        'sort_status': filter_status,
        'sort_priority': filter_priority,
        'sort_difficulty': filter_difficulty,
    }
    context.update(sort_context)
    update_context_for_pagination(context, 'issues', request, paginator, page)

    return TemplateResponse(request, template_name, context)
Exemplo n.º 17
0
def article_list(request, template_name="blog/article_list.html", extra_context=None):
    """
    Blog home page view, list all recently published article.
    :param request: The incoming request.
    :param template_name: The template name to be used.
    :param extra_context: Any extra context for the template.
    :return: TemplateResponse
    """

    # Retrieve all published articles
    published_articles = Article.objects.published().select_related("author").prefetch_related("tags", "categories")

    # Articles list pagination
    paginator, page = paginate(published_articles, request, NB_ARTICLES_PER_PAGE)

    # Render the template
    context = {"title": _("Articles")}
    update_context_for_pagination(context, "articles", request, paginator, page)
    if extra_context is not None:
        context.update(extra_context)

    return TemplateResponse(request, template_name, context)
Exemplo n.º 18
0
def notification_list(request, filterby='all',
                      template_name='notifications/notification_list.html',
                      extra_context=None):
    """
    Notifications list view, can display all notifications, unread ones or only read ones using ``filterby``
    parameter (valid options: None, 'read' or 'unread').
    :param request: The current request.
    :param filterby: The filtering option (None, read or unread)
    :param template_name: The template name to be used.
    :param extra_context: Any extra context for the template.
    :return: TemplateResponse
    """

    # Get the notifications list for the current user
    notifications = Notification.objects.filter(recipient=request.user)

    # Filter the list
    if filterby == 'read':
        notifications = notifications.filter(unread=False)
    elif filterby == 'unread':
        notifications = notifications.filter(unread=True)

    # Notifications list pagination
    paginator, page = paginate(notifications, request, NB_NOTIFICATIONS_PER_PAGE)

    # Render the template
    context = {
        'title': _('Notifications list'),
        'filter_by': filterby,
        'deletion_timeout_days': READ_NOTIFICATION_DELETION_TIMEOUT_DAYS
    }
    update_context_for_pagination(context, 'notifications', request, paginator, page)
    if extra_context is not None:
        context.update(extra_context)

    return TemplateResponse(request, template_name, context)
Exemplo n.º 19
0
def forum_thread_show(request, pk, slug,
                      template_name='forum/forum_thread_detail.html',
                      mark_as_unread_post_action='mark_unread',
                      extra_context=None):
    """
    Detail view of the given forum's thread.
    :param request: The current request.
    :param pk: The forum's thread's PK.
    :param slug: The forum's thread's slug.
    :param template_name: The template name to be used.
    :param mark_as_unread_post_action: "mark as unread" POST submit name.
    :param extra_context: Any extra context for the template.
    :return: TemplateResponse
    """
    assert slug is not None

    # Get the thread object by pk
    manager = ForumThread.objects.published().select_related('parent_forum', 'first_post__author')
    thread_obj = get_object_or_404(manager, pk=pk, slug=slug)

    # Handle private thread
    if not thread_obj.has_access(request.user):
        raise PermissionDenied()

    # Handle "mark as unread" action
    current_user = request.user
    if request.method == 'POST' and current_user.is_authenticated():
        if request.POST.get(mark_as_unread_post_action, None):

            # Mark as unread
            ReadForumThreadTracker.objects.mark_thread_as_unread(current_user, thread_obj)

            # Redirect to the parent forum
            return HttpResponseRedirect(thread_obj.parent_forum.get_absolute_url())

    # Paginate thread's posts
    paginator, page = paginate(thread_obj.posts.published().order_by('pub_date')
                               .select_related('author__user_profile', 'last_modification_by')
                               .prefetch_related('attachments'), request, NB_FORUM_POST_PER_PAGE)

    # Check if the current user has subscribed to the thread
    if current_user.is_authenticated():
        has_subscribe_to_thread = ForumThreadSubscription.objects.has_subscribed_to_thread(current_user, thread_obj)

        # Also handle read marker
        ReadForumThreadTracker.objects.mark_thread_as_read(current_user, thread_obj)
    else:
        has_subscribe_to_thread = False

    # Render the template
    context = {
        'title': _('Thread %s') % thread_obj.title,
        'thread': thread_obj,
        'forum': thread_obj.parent_forum,
        'has_subscribe_to_thread': has_subscribe_to_thread,
    }
    update_context_for_pagination(context, 'posts', request, paginator, page)
    if extra_context is not None:
        context.update(extra_context)

    return TemplateResponse(request, template_name, context)
Exemplo n.º 20
0
def forum_show(request, hierarchy,
               template_name='forum/forum_detail.html',
               extra_context=None):
    """
    Detail view for the specified forum or sub-forum.
    :param hierarchy: The desired forum's slug hierarchy.
    :param request: The current request.
    :param template_name: The template to be used.
    :param extra_context: Any extra context for the template.
    :return: TemplateResponse
    """
    assert hierarchy is not None

    # Get the forum object
    forum_obj = get_object_or_404(Forum, slug_hierarchy=hierarchy)

    # Check authorization if forum is private
    if not forum_obj.has_access(request.user):
        raise PermissionDenied()

    # Get all threads in this forum
    queryset = ForumThread.objects.display_ordered(forum_obj) \
        .select_related('first_post__author', 'last_post__author')

    # Prefetch posts and subscriptions
    current_user = request.user
    if current_user.is_authenticated():
        prefetch_posts = Prefetch('posts',
                                  queryset=ForumThreadPost.objects.filter(author=current_user),
                                  to_attr='user_posts')
        prefetch_subscriptions = Prefetch('subscribers',
                                          queryset=ForumThreadSubscription.objects.filter(user=current_user),
                                          to_attr='user_subscriptions')
        queryset = queryset.prefetch_related(prefetch_posts, prefetch_subscriptions)

    # Related thread list pagination
    paginator, page = paginate(queryset, request, NB_FORUM_THREAD_PER_PAGE)

    # Prefetch child forums
    if not page.has_previous():
        child_forums = forum_obj.children.all().order_by('ordering')
    else:
        # Avoid useless SQL request on other pages
        child_forums = None

    # Avoid useless SQL request for anonymous
    if current_user.is_authenticated():
        has_subscribe_to_forum = ForumSubscription.objects.has_subscribed_to_forum(current_user, forum_obj)
    else:
        has_subscribe_to_forum = False

    # Render the template
    context = {
        'title': _('Forum %s') % forum_obj.title,
        'forum': forum_obj,
        'child_forums': child_forums,
        'has_subscribe_to_forum': has_subscribe_to_forum
    }
    update_context_for_pagination(context, 'threads', request, paginator, page)
    if extra_context is not None:
        context.update(extra_context)

    # Prefetch read markers
    if current_user.is_authenticated():
        parent_forum_last_read_date = ReadForumTracker.objects.get_marker_for_forum(current_user, forum_obj)
        thread_markers = ReadForumThreadTracker.objects.get_marker_for_threads(current_user, page.object_list)
        context['read_markers'] = {
            'parent_forum_last_read_date': parent_forum_last_read_date,
            'thread_markers': thread_markers
        }

    return TemplateResponse(request, template_name, context)
Exemplo n.º 21
0
def ticket_show(request, pk,
                template_name='bugtracker/issueticket_detail.html',
                issue_comment_form=IssueCommentCreationForm,
                extra_context=None):
    """
    Detail view of the given ticket.
    comments can be reverse sorted (from new to old) using the GET parameter ``?sort=reverse``.
    :param pk: The ticket PK.
    :param template_name: The template to use for the view.
    :param issue_comment_form: The issue comment form to be used.
    :param request: The incoming request.
    :param extra_context: Any extra context dict.
    """

    # Reverse comments sorting
    if request.GET.get('sort', None) == 'reverse':
        order_by = '-pub_date'
        cur_order_by = 'reverse'
    else:
        order_by = 'pub_date'
        cur_order_by = ''

    # Get the ticket and related comments
    manager = IssueTicket.objects.select_related('submitter', 'assigned_to', 'component')
    issue_obj = get_object_or_404(manager, pk=pk)
    issue_comments_list = issue_obj.comments.order_by(order_by)

    # Issue's comments pagination
    paginator, page = paginate(issue_comments_list
                               .select_related('author')
                               .prefetch_related('changes'), request, NB_ISSUE_COMMENTS_PER_PAGE)

    # Get the user preferences
    current_user = request.user
    if current_user.is_authenticated():
        notify_of_reply = current_user.bugtracker_profile.notify_of_reply_by_default
        has_subscribe_to_issue = IssueTicketSubscription.objects.has_subscribed_to_issue(current_user, issue_obj)
    else:
        notify_of_reply = False
        has_subscribe_to_issue = False
    is_flooding = False

    # Handle POST requests
    if request.method == "POST":

        # Only authenticated user can post comment
        if not current_user.is_authenticated():
            raise PermissionDenied()

        # Refresh anti flood only on POST
        is_flooding = current_user.bugtracker_profile.is_flooding()

        # Handle form (with anti flood)
        form = issue_comment_form(request.POST)
        if form.is_valid() and not is_flooding:
            opts = {
                'request': request,
                'issue': issue_obj,
                'author': current_user,
                }
            new_obj = form.save(**opts)

            # Re-arm anti flood protection
            current_user.bugtracker_profile.rearm_flooding_delay_and_save()

            # Redirect to the newly created comment
            return HttpResponseRedirect(new_obj.get_absolute_url())
    else:
        form = issue_comment_form(initial={'notify_of_reply': notify_of_reply})

    # Render the template
    context = {
        'cur_order_by': cur_order_by,
        'is_flooding': is_flooding,
        'flood_delay_sec': NB_SECONDS_BETWEEN_COMMENTS,
        'issue': issue_obj,
        'has_subscribe_to_issue': has_subscribe_to_issue,
        'comment_form': form,
        'title': _('Ticket detail'),
        }
    update_context_for_pagination(context, 'issue_comments', request, paginator, page)
    if extra_context is not None:
        context.update(extra_context)

    return TemplateResponse(request, template_name, context)