forked from ScoDoc/DocScoDoc
PDF: affiche liste polices dispo, meilleurs messages d'erreur
This commit is contained in:
parent
1b887858bb
commit
41778d5918
@ -6,15 +6,13 @@
|
||||
|
||||
"""Génération bulletin BUT au format PDF standard
|
||||
"""
|
||||
from reportlab.lib.colors import blue
|
||||
from reportlab.lib.units import cm, mm
|
||||
from reportlab.platypus import Paragraph, Spacer
|
||||
|
||||
from app.scodoc.sco_pdf import blue, cm, mm
|
||||
|
||||
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
||||
from app.scodoc import gen_tables
|
||||
from app.scodoc.sco_codes_parcours import UE_SPORT
|
||||
from app.scodoc.sco_utils import fmt_note
|
||||
|
||||
from app.scodoc.sco_bulletins_standard import BulletinGeneratorStandard
|
||||
|
||||
|
||||
class BulletinGeneratorStandardBUT(BulletinGeneratorStandard):
|
||||
|
@ -34,17 +34,19 @@
|
||||
CE FORMAT N'EVOLUERA PLUS ET EST CONSIDERE COMME OBSOLETE.
|
||||
|
||||
"""
|
||||
from reportlab.lib.colors import Color, blue
|
||||
from reportlab.lib.units import cm, mm
|
||||
from reportlab.platypus import Paragraph, Spacer, Table
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc import sco_pdf
|
||||
from app.scodoc.sco_pdf import Color, Paragraph, Spacer, Table
|
||||
from app.scodoc.sco_pdf import blue, cm, mm
|
||||
from app.scodoc.sco_pdf import SU
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_bulletins_generator
|
||||
from app.scodoc import sco_bulletins_pdf
|
||||
from app.scodoc import sco_formsemestre
|
||||
from app.scodoc.sco_permissions import Permission
|
||||
from app.scodoc import sco_pdf
|
||||
from app.scodoc.sco_pdf import SU
|
||||
from app.scodoc import sco_preferences
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
|
||||
# Important: Le nom de la classe ne doit pas changer (bien le choisir), car il sera stocké en base de données (dans les préférences)
|
||||
class BulletinGeneratorLegacy(sco_bulletins_generator.BulletinGenerator):
|
||||
|
@ -32,16 +32,12 @@ On redéfini la table centrale du bulletin de note et hérite de tout le reste d
|
||||
|
||||
E. Viennet, juillet 2011
|
||||
"""
|
||||
from reportlab.lib.colors import Color
|
||||
from reportlab.lib.units import mm
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_pdf import blue, cm, mm
|
||||
from app.scodoc.sco_pdf import Color, Paragraph, Spacer, Table
|
||||
|
||||
from app.scodoc import sco_preferences
|
||||
|
||||
from app.scodoc import sco_bulletins_generator
|
||||
from app.scodoc import sco_bulletins_standard
|
||||
from app.scodoc import gen_tables
|
||||
from app.scodoc import sco_preferences
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
|
||||
class BulletinGeneratorUCAC(sco_bulletins_standard.BulletinGeneratorStandard):
|
||||
|
@ -44,22 +44,17 @@ import traceback
|
||||
import unicodedata
|
||||
|
||||
import reportlab
|
||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Frame, PageBreak
|
||||
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.platypus import Paragraph, Frame
|
||||
from reportlab.platypus.flowables import Flowable
|
||||
from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate
|
||||
from reportlab.lib.styles import getSampleStyleSheet
|
||||
from reportlab.rl_config import defaultPageSize # pylint: disable=no-name-in-module
|
||||
from reportlab.lib.units import inch, cm, mm
|
||||
from reportlab.lib.colors import pink, black, red, blue, green, magenta, red
|
||||
from reportlab.lib.colors import Color
|
||||
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
|
||||
from reportlab.lib import styles
|
||||
from reportlab.lib.pagesizes import letter, A4, landscape
|
||||
|
||||
|
||||
from flask import g
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_utils import CONFIG
|
||||
from app import log
|
||||
from app.scodoc.sco_exceptions import ScoGenError, ScoValueError
|
||||
@ -89,6 +84,12 @@ def SU(s):
|
||||
return s
|
||||
|
||||
|
||||
def get_available_font_names() -> list[str]:
|
||||
"""List installed font names"""
|
||||
can = canvas.Canvas(io.StringIO())
|
||||
return can.getAvailableFonts()
|
||||
|
||||
|
||||
def _splitPara(txt):
|
||||
"split a string, returns a list of <para > ... </para>"
|
||||
L = []
|
||||
@ -147,12 +148,26 @@ def makeParas(txt, style, suppress_empty=False):
|
||||
except Exception as e:
|
||||
log(traceback.format_exc())
|
||||
log("Invalid pdf para format: %s" % txt)
|
||||
result = [
|
||||
Paragraph(
|
||||
SU('<font color="red"><i>Erreur: format invalide</i></font>'),
|
||||
style,
|
||||
)
|
||||
]
|
||||
try:
|
||||
result = [
|
||||
Paragraph(
|
||||
SU('<font color="red"><i>Erreur: format invalide</i></font>'),
|
||||
style,
|
||||
)
|
||||
]
|
||||
except ValueError as e: # probleme font ? essaye sans style
|
||||
# recupere font en cause ?
|
||||
m = re.match(r".*family/bold/italic for (.*)", e.args[0], re.DOTALL)
|
||||
if m:
|
||||
message = f"police non disponible: {m[1]}"
|
||||
else:
|
||||
message = "format invalide"
|
||||
return [
|
||||
Paragraph(
|
||||
SU(f'<font color="red"><b>Erreur: {message}</b></font>'),
|
||||
reportlab.lib.styles.ParagraphStyle({}),
|
||||
)
|
||||
]
|
||||
return result
|
||||
|
||||
|
||||
@ -166,7 +181,6 @@ def bold_paras(L, tag="b", close=None):
|
||||
if hasattr(L, "keys"):
|
||||
# L is a dict
|
||||
for k in L:
|
||||
x = L[k]
|
||||
if k[0] != "_":
|
||||
L[k] = b + L[k] or "" + close
|
||||
return L
|
||||
@ -256,7 +270,7 @@ class ScoDocPageTemplate(PageTemplate):
|
||||
if logo is not None:
|
||||
self.background_image_filename = logo.filepath
|
||||
|
||||
def beforeDrawPage(self, canvas, doc):
|
||||
def beforeDrawPage(self, canv, doc):
|
||||
"""Draws (optional) background, logo and contribution message on each page.
|
||||
|
||||
day : Day of the month as a decimal number [01,31]
|
||||
@ -271,10 +285,10 @@ class ScoDocPageTemplate(PageTemplate):
|
||||
"""
|
||||
if not self.preferences:
|
||||
return
|
||||
canvas.saveState()
|
||||
canv.saveState()
|
||||
# ---- Background image
|
||||
if self.background_image_filename and self.with_page_background:
|
||||
canvas.drawImage(
|
||||
canv.drawImage(
|
||||
self.background_image_filename, 0, 0, doc.pagesize[0], doc.pagesize[1]
|
||||
)
|
||||
|
||||
@ -285,32 +299,32 @@ class ScoDocPageTemplate(PageTemplate):
|
||||
(width, height),
|
||||
image,
|
||||
) = self.logo
|
||||
canvas.drawImage(image, inch, doc.pagesize[1] - inch, width, height)
|
||||
canv.drawImage(image, inch, doc.pagesize[1] - inch, width, height)
|
||||
|
||||
# ---- Add some meta data and bookmarks
|
||||
if self.pdfmeta_author:
|
||||
canvas.setAuthor(SU(self.pdfmeta_author))
|
||||
canv.setAuthor(SU(self.pdfmeta_author))
|
||||
if self.pdfmeta_title:
|
||||
canvas.setTitle(SU(self.pdfmeta_title))
|
||||
canv.setTitle(SU(self.pdfmeta_title))
|
||||
if self.pdfmeta_subject:
|
||||
canvas.setSubject(SU(self.pdfmeta_subject))
|
||||
canv.setSubject(SU(self.pdfmeta_subject))
|
||||
|
||||
bookmark = self.pagesbookmarks.get(doc.page, None)
|
||||
if bookmark:
|
||||
canvas.bookmarkPage(bookmark)
|
||||
canvas.addOutlineEntry(SU(bookmark), bookmark)
|
||||
canv.bookmarkPage(bookmark)
|
||||
canv.addOutlineEntry(SU(bookmark), bookmark)
|
||||
|
||||
def draw_footer(self, canvas, content):
|
||||
def draw_footer(self, canv, content):
|
||||
"""Print the footer"""
|
||||
canvas.setFont(
|
||||
canv.setFont(
|
||||
self.preferences["SCOLAR_FONT"], self.preferences["SCOLAR_FONT_SIZE_FOOT"]
|
||||
)
|
||||
canvas.drawString(
|
||||
canv.drawString(
|
||||
self.preferences["pdf_footer_x"] * mm,
|
||||
self.preferences["pdf_footer_y"] * mm,
|
||||
content,
|
||||
)
|
||||
canvas.restoreState()
|
||||
canv.restoreState()
|
||||
|
||||
def footer_string(self) -> str:
|
||||
"""String contenu du pied de page"""
|
||||
@ -319,14 +333,14 @@ class ScoDocPageTemplate(PageTemplate):
|
||||
d["server_url"] = self.server_name
|
||||
return SU(self.footer_template % d)
|
||||
|
||||
def afterDrawPage(self, canvas, doc):
|
||||
def afterDrawPage(self, canv, doc):
|
||||
if not self.preferences:
|
||||
return
|
||||
# ---- Footer
|
||||
foot_content = None
|
||||
if hasattr(doc, "current_footer"):
|
||||
foot_content = doc.current_footer
|
||||
self.draw_footer(canvas, foot_content or self.footer_string())
|
||||
self.draw_footer(canv, foot_content or self.footer_string())
|
||||
# ---- Filigranne (texte en diagonal en haut a gauche de chaque page)
|
||||
filigranne = None
|
||||
if hasattr(doc, "filigranne"):
|
||||
@ -338,13 +352,13 @@ class ScoDocPageTemplate(PageTemplate):
|
||||
else:
|
||||
filigranne = self.filigranne.get(doc.page, None)
|
||||
if filigranne:
|
||||
canvas.saveState()
|
||||
canvas.translate(9 * cm, 27.6 * cm)
|
||||
canvas.rotate(30)
|
||||
canvas.scale(4.5, 4.5)
|
||||
canvas.setFillColorRGB(1.0, 0.65, 0.65, alpha=0.6)
|
||||
canvas.drawRightString(0, 0, SU(filigranne))
|
||||
canvas.restoreState()
|
||||
canv.saveState()
|
||||
canv.translate(9 * cm, 27.6 * cm)
|
||||
canv.rotate(30)
|
||||
canv.scale(4.5, 4.5)
|
||||
canv.setFillColorRGB(1.0, 0.65, 0.65, alpha=0.6)
|
||||
canv.drawRightString(0, 0, SU(filigranne))
|
||||
canv.restoreState()
|
||||
doc.filigranne = None
|
||||
|
||||
def afterPage(self):
|
||||
@ -443,8 +457,8 @@ class PDFLock(object):
|
||||
return # deja lock pour ce thread
|
||||
try:
|
||||
self.Q.put(1, True, self.timeout)
|
||||
except queue.Full:
|
||||
raise ScoGenError(msg="Traitement PDF occupé: ré-essayez")
|
||||
except queue.Full as e:
|
||||
raise ScoGenError(msg="Traitement PDF occupé: ré-essayez") from e
|
||||
self.current_thread = threading.get_ident()
|
||||
self.nref = 1
|
||||
log("PDFLock: granted to %s" % self.current_thread)
|
||||
@ -471,7 +485,6 @@ class WatchLock:
|
||||
def release(self):
|
||||
t = threading.current_thread()
|
||||
assert (self.native_id == t.native_id) and (self.ident == t.ident)
|
||||
pass
|
||||
|
||||
|
||||
class FakeLock:
|
||||
|
@ -121,6 +121,7 @@ from app import log
|
||||
from app.scodoc.sco_exceptions import ScoValueError, ScoException
|
||||
from app.scodoc.TrivialFormulator import TrivialFormulator
|
||||
import app.scodoc.notesdb as ndb
|
||||
from app.scodoc import sco_pdf
|
||||
import app.scodoc.sco_utils as scu
|
||||
|
||||
|
||||
@ -194,6 +195,8 @@ def _get_pref_default_value_from_config(name, pref_spec):
|
||||
return value
|
||||
|
||||
|
||||
_INSTALLED_FONTS = ", ".join(sco_pdf.get_available_font_names())
|
||||
|
||||
PREF_CATEGORIES = (
|
||||
# sur page "Paramètres"
|
||||
("general", {}),
|
||||
@ -777,7 +780,7 @@ class BasePreferences(object):
|
||||
{
|
||||
"initvalue": "Helvetica",
|
||||
"title": "Police de caractère principale",
|
||||
"explanation": "pour les pdf (Helvetica est recommandée)",
|
||||
"explanation": f"pour les pdf (Helvetica est recommandée, parmi {_INSTALLED_FONTS})",
|
||||
"size": 25,
|
||||
"category": "pdf",
|
||||
},
|
||||
@ -1140,7 +1143,7 @@ class BasePreferences(object):
|
||||
{
|
||||
"initvalue": "Times-Roman",
|
||||
"title": "Police de caractère pour les PV",
|
||||
"explanation": "pour les pdf",
|
||||
"explanation": f"pour les pdf ({_INSTALLED_FONTS})",
|
||||
"size": 25,
|
||||
"category": "pvpdf",
|
||||
},
|
||||
@ -1542,7 +1545,7 @@ class BasePreferences(object):
|
||||
{
|
||||
"initvalue": "Times-Roman",
|
||||
"title": "Police titres bulletins",
|
||||
"explanation": "pour les pdf",
|
||||
"explanation": f"pour les pdf ({_INSTALLED_FONTS})",
|
||||
"size": 25,
|
||||
"category": "bul",
|
||||
},
|
||||
|
@ -28,15 +28,13 @@
|
||||
"""Edition des PV de jury
|
||||
"""
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
|
||||
import reportlab
|
||||
from reportlab.lib.units import cm, mm
|
||||
from reportlab.lib.enums import TA_LEFT, TA_RIGHT, TA_CENTER, TA_JUSTIFY
|
||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Frame, PageBreak
|
||||
from reportlab.platypus import Table, TableStyle, Image, KeepInFrame
|
||||
from reportlab.platypus.flowables import Flowable
|
||||
from reportlab.lib.enums import TA_RIGHT, TA_JUSTIFY
|
||||
from reportlab.platypus import Paragraph, Spacer, Frame, PageBreak
|
||||
from reportlab.platypus import Table, TableStyle, Image
|
||||
from reportlab.platypus.doctemplate import PageTemplate, BaseDocTemplate
|
||||
from reportlab.lib.pagesizes import A4, landscape
|
||||
from reportlab.lib import styles
|
||||
@ -53,7 +51,6 @@ from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_etud
|
||||
import sco_version
|
||||
from app.scodoc.sco_logos import find_logo
|
||||
from app.scodoc.sco_pdf import PDFLOCK
|
||||
from app.scodoc.sco_pdf import SU
|
||||
|
||||
LOGO_FOOTER_ASPECT = scu.CONFIG.LOGO_FOOTER_ASPECT # XXX A AUTOMATISER
|
||||
@ -317,14 +314,14 @@ class PVTemplate(CourrierIndividuelTemplate):
|
||||
self.with_footer = self.preferences["PV_WITH_FOOTER"]
|
||||
self.with_page_background = self.preferences["PV_WITH_BACKGROUND"]
|
||||
|
||||
def afterDrawPage(self, canvas, doc):
|
||||
def afterDrawPage(self, canv, doc):
|
||||
"""Called after all flowables have been drawn on a page"""
|
||||
pass
|
||||
|
||||
def beforeDrawPage(self, canvas, doc):
|
||||
def beforeDrawPage(self, canv, doc):
|
||||
"""Called before any flowables are drawn on a page"""
|
||||
# If the page number is even, force a page break
|
||||
CourrierIndividuelTemplate.beforeDrawPage(self, canvas, doc)
|
||||
CourrierIndividuelTemplate.beforeDrawPage(self, canv, doc)
|
||||
# Note: on cherche un moyen de generer un saut de page double
|
||||
# (redémarrer sur page impaire, nouvelle feuille en recto/verso). Pas trouvé en Platypus.
|
||||
#
|
||||
|
@ -33,20 +33,23 @@
|
||||
import io
|
||||
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib import pagesizes
|
||||
from reportlab.lib.colors import black
|
||||
from reportlab.lib.pagesizes import A4, A3
|
||||
from reportlab.lib import styles
|
||||
from reportlab.lib.pagesizes import landscape
|
||||
from reportlab.lib.units import cm
|
||||
from reportlab.platypus import KeepInFrame, Paragraph, Table, TableStyle
|
||||
from reportlab.platypus.doctemplate import BaseDocTemplate
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app import log
|
||||
from app.scodoc import sco_abs
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc.sco_exceptions import ScoPDFFormatError
|
||||
from app.scodoc import sco_groups
|
||||
from app.scodoc import sco_groups_view
|
||||
from app.scodoc import sco_preferences
|
||||
from app.scodoc import sco_trombino
|
||||
from app.scodoc import sco_etud
|
||||
from app.scodoc.sco_exceptions import ScoPDFFormatError
|
||||
from app.scodoc.sco_pdf import *
|
||||
|
||||
import app.scodoc.sco_utils as scu
|
||||
from app.scodoc.sco_pdf import SU, ScoDocPageTemplate
|
||||
|
||||
# Paramétrage de l'aspect graphique:
|
||||
PHOTOWIDTH = 2.8 * cm
|
||||
@ -55,7 +58,7 @@ N_PER_ROW = 5
|
||||
|
||||
|
||||
def pdf_trombino_tours(
|
||||
group_ids=[], # liste des groupes à afficher
|
||||
group_ids=(), # liste des groupes à afficher
|
||||
formsemestre_id=None, # utilisé si pas de groupes selectionné
|
||||
):
|
||||
"""Generation du trombinoscope en fichier PDF"""
|
||||
@ -66,7 +69,6 @@ def pdf_trombino_tours(
|
||||
|
||||
DeptName = sco_preferences.get_preference("DeptName")
|
||||
DeptFullName = sco_preferences.get_preference("DeptFullName")
|
||||
UnivName = sco_preferences.get_preference("UnivName")
|
||||
InstituteName = sco_preferences.get_preference("InstituteName")
|
||||
# Generate PDF page
|
||||
StyleSheet = styles.getSampleStyleSheet()
|
||||
@ -143,9 +145,7 @@ def pdf_trombino_tours(
|
||||
|
||||
for group_id in groups_infos.group_ids:
|
||||
if group_id != "None":
|
||||
members, group, group_tit, sem, nbdem = sco_groups.get_group_infos(
|
||||
group_id, "I"
|
||||
)
|
||||
members, _, group_tit, sem, _ = sco_groups.get_group_infos(group_id, "I")
|
||||
groups += " %s" % group_tit
|
||||
L = []
|
||||
currow = []
|
||||
@ -286,14 +286,14 @@ def pdf_trombino_tours(
|
||||
|
||||
|
||||
def pdf_feuille_releve_absences(
|
||||
group_ids=[], # liste des groupes à afficher
|
||||
group_ids=(), # liste des groupes à afficher
|
||||
formsemestre_id=None, # utilisé si pas de groupes selectionné
|
||||
):
|
||||
"""Generation de la feuille d'absence en fichier PDF, avec photos"""
|
||||
|
||||
NB_CELL_AM = sco_preferences.get_preference("feuille_releve_abs_AM")
|
||||
NB_CELL_PM = sco_preferences.get_preference("feuille_releve_abs_PM")
|
||||
COLWIDTH = 0.85 * cm
|
||||
col_width = 0.85 * cm
|
||||
if sco_preferences.get_preference("feuille_releve_abs_samedi"):
|
||||
days = sco_abs.DAYNAMES[:6] # Lundi, ..., Samedi
|
||||
else:
|
||||
@ -307,7 +307,6 @@ def pdf_feuille_releve_absences(
|
||||
|
||||
DeptName = sco_preferences.get_preference("DeptName")
|
||||
DeptFullName = sco_preferences.get_preference("DeptFullName")
|
||||
UnivName = sco_preferences.get_preference("UnivName")
|
||||
InstituteName = sco_preferences.get_preference("InstituteName")
|
||||
# Generate PDF page
|
||||
StyleSheet = styles.getSampleStyleSheet()
|
||||
@ -340,7 +339,7 @@ def pdf_feuille_releve_absences(
|
||||
currow = [""] * (NB_CELL_AM + 1 + NB_CELL_PM + 1)
|
||||
elem_day = Table(
|
||||
[currow],
|
||||
colWidths=([COLWIDTH] * (NB_CELL_AM + 1 + NB_CELL_PM + 1)),
|
||||
colWidths=([col_width] * (NB_CELL_AM + 1 + NB_CELL_PM + 1)),
|
||||
style=TableStyle(
|
||||
[
|
||||
("GRID", (0, 0), (NB_CELL_AM - 1, 0), 0.25, black),
|
||||
@ -362,7 +361,7 @@ def pdf_feuille_releve_absences(
|
||||
|
||||
elem_week = Table(
|
||||
W,
|
||||
colWidths=([COLWIDTH * (NB_CELL_AM + 1 + NB_CELL_PM + 1)] * nb_days),
|
||||
colWidths=([col_width * (NB_CELL_AM + 1 + NB_CELL_PM + 1)] * nb_days),
|
||||
style=TableStyle(
|
||||
[
|
||||
("LEFTPADDING", (0, 0), (-1, -1), 0),
|
||||
@ -378,7 +377,7 @@ def pdf_feuille_releve_absences(
|
||||
|
||||
elem_day_name = Table(
|
||||
[currow],
|
||||
colWidths=([COLWIDTH * (NB_CELL_AM + 1 + NB_CELL_PM + 1)] * nb_days),
|
||||
colWidths=([col_width * (NB_CELL_AM + 1 + NB_CELL_PM + 1)] * nb_days),
|
||||
style=TableStyle(
|
||||
[
|
||||
("LEFTPADDING", (0, 0), (-1, -1), 0),
|
||||
@ -390,9 +389,7 @@ def pdf_feuille_releve_absences(
|
||||
)
|
||||
|
||||
for group_id in groups_infos.group_ids:
|
||||
members, group, group_tit, sem, nbdem = sco_groups.get_group_infos(
|
||||
group_id, "I"
|
||||
)
|
||||
members, _, group_tit, _, _ = sco_groups.get_group_infos(group_id, "I")
|
||||
L = []
|
||||
|
||||
currow = [
|
||||
@ -429,7 +426,10 @@ def pdf_feuille_releve_absences(
|
||||
T = Table(
|
||||
L,
|
||||
colWidths=(
|
||||
[5.0 * cm, (COLWIDTH * (NB_CELL_AM + 1 + NB_CELL_PM + 1) * nb_days)]
|
||||
[
|
||||
5.0 * cm,
|
||||
(col_width * (NB_CELL_AM + 1 + NB_CELL_PM + 1) * nb_days),
|
||||
]
|
||||
),
|
||||
style=TableStyle(
|
||||
[
|
||||
|
Loading…
Reference in New Issue
Block a user