Séparation des vues Tag
This commit is contained in:
parent
e127a25a0e
commit
76610993ba
@ -1,5 +1,5 @@
|
||||
default_language_version:
|
||||
python: python3.12
|
||||
python: python3
|
||||
repos:
|
||||
# Using this mirror lets us use mypyc-compiled black, which is about 2x faster
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
|
@ -17,25 +17,31 @@ from .sitemaps import (
|
||||
)
|
||||
from .models import Event, Place, Organisation, Category
|
||||
from .views import (
|
||||
# Errors
|
||||
# Errors
|
||||
internal_server_error,
|
||||
page_not_found,
|
||||
# General pages
|
||||
# General pages
|
||||
about,
|
||||
import_requirements,
|
||||
mentions_legales,
|
||||
moderation_rules,
|
||||
thank_you,
|
||||
# TODO pas encore trié
|
||||
# tags
|
||||
view_tag,
|
||||
view_tag_past,
|
||||
TagUpdateView,
|
||||
tag_list,
|
||||
TagDeleteView,
|
||||
rename_tag,
|
||||
delete_tag,
|
||||
TagCreateView,
|
||||
# TODO pas encore trié
|
||||
home,
|
||||
week_view,
|
||||
month_view,
|
||||
day_view,
|
||||
upcoming_events,
|
||||
export_ical,
|
||||
view_tag,
|
||||
view_tag_past,
|
||||
TagUpdateView,
|
||||
recent,
|
||||
administration,
|
||||
activite,
|
||||
@ -64,11 +70,6 @@ from .views import (
|
||||
cancel_import,
|
||||
run_all_fb_rimports,
|
||||
run_all_rimports,
|
||||
tag_list,
|
||||
TagDeleteView,
|
||||
rename_tag,
|
||||
delete_tag,
|
||||
TagCreateView,
|
||||
EventDetailView,
|
||||
EventUpdateView,
|
||||
EventModerateView,
|
||||
|
@ -1,3 +1,4 @@
|
||||
from .oldviews import *
|
||||
from .errors import *
|
||||
from .general_pages_views import *
|
||||
from .tag_views import *
|
||||
|
@ -79,8 +79,6 @@ from ..forms import (
|
||||
RecurrentImportForm,
|
||||
SelectEventInList,
|
||||
SimpleContactForm,
|
||||
TagForm,
|
||||
TagRenameForm,
|
||||
URLSubmissionFormSet,
|
||||
URLSubmissionFormWithContact,
|
||||
UserProfileForm,
|
||||
@ -97,11 +95,11 @@ from ..models import (
|
||||
Place,
|
||||
RecurrentImport,
|
||||
StaticContent,
|
||||
Tag,
|
||||
remove_accents,
|
||||
UserProfile,
|
||||
)
|
||||
from ..utils import PlaceGuesser
|
||||
from .utils import get_event_qs
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -161,26 +159,6 @@ class PaginatorFilter(Paginator):
|
||||
return page
|
||||
|
||||
|
||||
#
|
||||
#
|
||||
# Useful for translation
|
||||
to_be_translated = [
|
||||
_("Recurrent import name"),
|
||||
_("Add another"),
|
||||
_("Browse..."),
|
||||
_("No file selected."),
|
||||
]
|
||||
|
||||
|
||||
def get_event_qs(request):
|
||||
if request.user.is_authenticated:
|
||||
return Event.objects.filter()
|
||||
else:
|
||||
return Event.objects.filter(status=Event.STATUS.PUBLISHED)
|
||||
|
||||
|
||||
|
||||
|
||||
def home(request, cat=None):
|
||||
return week_view(request, home=True, cat=cat)
|
||||
|
||||
@ -2622,84 +2600,6 @@ class OrganisationDeleteView(PermissionRequiredMixin, DeleteView):
|
||||
success_url = reverse_lazy("view_organisations")
|
||||
|
||||
|
||||
#########################
|
||||
## Tags
|
||||
#########################
|
||||
|
||||
|
||||
class TagUpdateView(PermissionRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
model = Tag
|
||||
permission_required = "agenda_culturel.change_tag"
|
||||
form_class = TagForm
|
||||
success_message = _("The tag has been successfully updated.")
|
||||
|
||||
|
||||
class TagCreateView(PermissionRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = Tag
|
||||
permission_required = "agenda_culturel.add_tag"
|
||||
form_class = TagForm
|
||||
success_message = _("The tag has been successfully created.")
|
||||
|
||||
def get_initial(self, *args, **kwargs):
|
||||
initial = super().get_initial(**kwargs)
|
||||
if "name" in self.request.GET:
|
||||
initial["name"] = self.request.GET.get("name")
|
||||
return initial
|
||||
|
||||
def form_valid(self, form):
|
||||
Tag.clear_cache()
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class TagDeleteView(PermissionRequiredMixin, DeleteView):
|
||||
model = Tag
|
||||
permission_required = "agenda_culturel.delete_tag"
|
||||
success_url = reverse_lazy("view_all_tags")
|
||||
|
||||
|
||||
def view_tag_past(request, t):
|
||||
return view_tag(request, t, True)
|
||||
|
||||
|
||||
def view_tag(request, t, past=False):
|
||||
now = date.today()
|
||||
|
||||
qs = get_event_qs(request).filter(tags__contains=[t])
|
||||
|
||||
if past:
|
||||
qs = qs.filter(start_day__lt=now).order_by("-start_day", "-start_time")
|
||||
else:
|
||||
qs = qs.filter(start_day__gte=now).order_by("start_day", "start_time")
|
||||
|
||||
qs = qs.filter(
|
||||
Q(other_versions__isnull=True)
|
||||
| Q(other_versions__representative=F("pk"))
|
||||
| Q(other_versions__representative__isnull=True)
|
||||
)
|
||||
|
||||
paginator = Paginator(qs, 10)
|
||||
page = request.GET.get("page")
|
||||
|
||||
try:
|
||||
response = paginator.page(page)
|
||||
except PageNotAnInteger:
|
||||
response = paginator.page(1)
|
||||
except EmptyPage:
|
||||
response = paginator.page(paginator.num_pages)
|
||||
|
||||
rimports = RecurrentImport.objects.filter(defaultTags__contains=[t])
|
||||
|
||||
tag = Tag.objects.filter(name=t).first()
|
||||
context = {
|
||||
"tag": t,
|
||||
"paginator_filter": response,
|
||||
"object": tag,
|
||||
"rimports": rimports,
|
||||
"past": past,
|
||||
}
|
||||
return render(request, "agenda_culturel/tag.html", context)
|
||||
|
||||
|
||||
def statistics(request, pk=None):
|
||||
if pk is not None:
|
||||
rimport = RecurrentImport.objects.filter(pk=pk)
|
||||
@ -2811,171 +2711,6 @@ def statistics(request, pk=None):
|
||||
return render(request, "agenda_culturel/rimport-statistics.html", context)
|
||||
|
||||
|
||||
def tag_list(request):
|
||||
tags = Event.get_all_tags()
|
||||
r_tags = [t["tag"] for t in tags]
|
||||
objects = Tag.objects.order_by("name").all()
|
||||
d_objects = dict()
|
||||
for o in objects:
|
||||
d_objects[o.name] = o
|
||||
|
||||
tags = [
|
||||
t | {"obj": d_objects[t["tag"]]} if t["tag"] in d_objects else t for t in tags
|
||||
]
|
||||
tags += [
|
||||
{"obj": o, "tag": o.name, "count": 0} for o in objects if o.name not in r_tags
|
||||
]
|
||||
|
||||
context = {
|
||||
"tags": sorted(
|
||||
tags,
|
||||
key=lambda x: emoji.demojize(
|
||||
remove_accents(x["tag"]).lower(), delimiters=("000", "")
|
||||
),
|
||||
)
|
||||
}
|
||||
return render(request, "agenda_culturel/tags.html", context)
|
||||
|
||||
|
||||
@login_required(login_url="/accounts/login/")
|
||||
@permission_required("agenda_culturel.change_tag")
|
||||
def rename_tag(request, t):
|
||||
form = TagRenameForm(name=t)
|
||||
|
||||
if request.method == "POST":
|
||||
form = TagRenameForm(request.POST, name=t)
|
||||
if form.is_valid():
|
||||
save = True
|
||||
if form.cleaned_data["name"] == t:
|
||||
messages.warning(
|
||||
request,
|
||||
_("You have not modified the tag name."),
|
||||
)
|
||||
save = False
|
||||
elif not form.is_force():
|
||||
if (
|
||||
Event.objects.filter(
|
||||
tags__contains=[form.cleaned_data["name"]]
|
||||
).count()
|
||||
> 0
|
||||
):
|
||||
if Tag.objects.filter(name=form.cleaned_data["name"]):
|
||||
messages.warning(
|
||||
request,
|
||||
(
|
||||
_(
|
||||
"This tag {} is already in use, and is described by different information from the current tag. You can force renaming by checking the corresponding option. The information associated with tag {} will be deleted, and all events associated with tag {} will be associated with tag {}."
|
||||
)
|
||||
).format(
|
||||
form.cleaned_data["name"],
|
||||
t,
|
||||
t,
|
||||
form.cleaned_data["name"],
|
||||
),
|
||||
)
|
||||
else:
|
||||
messages.warning(
|
||||
request,
|
||||
(
|
||||
_(
|
||||
"This tag {} is already in use. You can force renaming by checking the corresponding option."
|
||||
)
|
||||
).format(form.cleaned_data["name"]),
|
||||
)
|
||||
save = False
|
||||
form = TagRenameForm(request.POST, name=t, force=True)
|
||||
|
||||
if save:
|
||||
# find all matching events and update them
|
||||
events = Event.objects.filter(tags__contains=[t])
|
||||
new_name = form.cleaned_data["name"]
|
||||
for e in events:
|
||||
e.tags = [te for te in e.tags if te != t]
|
||||
if new_name not in e.tags:
|
||||
e.tags += [new_name]
|
||||
Event.objects.bulk_update(events, fields=["tags"])
|
||||
|
||||
# find all recurrent imports and fix them
|
||||
rimports = RecurrentImport.objects.filter(defaultTags__contains=[t])
|
||||
for ri in rimports:
|
||||
ri.tags = [te for te in ri.defaultTags if te != t]
|
||||
if new_name not in ri.tags:
|
||||
ri.tags += [new_name]
|
||||
RecurrentImport.objects.bulk_update(rimports, fields=["defaultTags"])
|
||||
|
||||
# find tag object
|
||||
tag_object = Tag.objects.filter(name=t).first()
|
||||
if tag_object:
|
||||
tag_object.name = new_name
|
||||
tag_object.save()
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
(_("The tag {} has been successfully renamed to {}.")).format(
|
||||
t, form.cleaned_data["name"]
|
||||
),
|
||||
)
|
||||
return HttpResponseRedirect(
|
||||
reverse_lazy("view_tag", kwargs={"t": form.cleaned_data["name"]})
|
||||
)
|
||||
|
||||
nb = Event.objects.filter(tags__contains=[t]).count()
|
||||
|
||||
return render(
|
||||
request,
|
||||
"agenda_culturel/tag_rename_form.html",
|
||||
context={"form": form, "tag": t, "nb": nb},
|
||||
)
|
||||
|
||||
|
||||
@login_required(login_url="/accounts/login/")
|
||||
@permission_required("agenda_culturel.delete_tag")
|
||||
def delete_tag(request, t):
|
||||
respage = reverse_lazy("view_all_tags")
|
||||
|
||||
if request.method == "POST":
|
||||
# remove tag from events
|
||||
events = Event.objects.filter(tags__contains=[t])
|
||||
for e in events:
|
||||
e.tags = [te for te in e.tags if te != t]
|
||||
Event.objects.bulk_update(events, fields=["tags"])
|
||||
|
||||
# remove tag from recurrent imports
|
||||
rimports = RecurrentImport.objects.filter(defaultTags__contains=[t])
|
||||
for ri in rimports:
|
||||
ri.tags = [te for te in ri.defaultTags if te != t]
|
||||
RecurrentImport.objects.bulk_update(rimports, fields=["defaultTags"])
|
||||
|
||||
# find tag object
|
||||
tag_object = Tag.objects.filter(name=t).first()
|
||||
if tag_object:
|
||||
tag_object.delete()
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
(_("The tag {} has been successfully deleted.")).format(t),
|
||||
)
|
||||
return HttpResponseRedirect(respage)
|
||||
else:
|
||||
nb = Event.objects.filter(tags__contains=[t]).count()
|
||||
obj = Tag.objects.filter(name=t).first()
|
||||
nbi = RecurrentImport.objects.filter(defaultTags__contains=[t]).count()
|
||||
cancel_url = request.META.get("HTTP_REFERER", "")
|
||||
if cancel_url == "":
|
||||
cancel_url = respage
|
||||
return render(
|
||||
request,
|
||||
"agenda_culturel/tag_confirm_delete_by_name.html",
|
||||
{
|
||||
"tag": t,
|
||||
"nb": nb,
|
||||
"nbi": nbi,
|
||||
"cancel_url": cancel_url,
|
||||
"obj": obj,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def clear_cache(request):
|
||||
if request.method == "POST":
|
||||
cache.clear()
|
||||
|
270
src/agenda_culturel/views/tag_views.py
Normal file
270
src/agenda_culturel/views/tag_views.py
Normal file
@ -0,0 +1,270 @@
|
||||
from django.contrib.auth.decorators import login_required, permission_required
|
||||
from django.contrib.auth.mixins import (
|
||||
PermissionRequiredMixin,
|
||||
)
|
||||
from django.contrib.messages.views import SuccessMessageMixin
|
||||
from django.views.generic.edit import (
|
||||
CreateView,
|
||||
DeleteView,
|
||||
UpdateView,
|
||||
)
|
||||
from ..forms import (
|
||||
TagForm,
|
||||
TagRenameForm,
|
||||
)
|
||||
from ..models import (
|
||||
Tag,
|
||||
RecurrentImport,
|
||||
Event,
|
||||
remove_accents,
|
||||
)
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.urls import reverse_lazy
|
||||
from datetime import date
|
||||
from .utils import get_event_qs
|
||||
from django.db.models import F, Q
|
||||
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
||||
from django.shortcuts import render
|
||||
from django.contrib import messages
|
||||
from django.http import (
|
||||
HttpResponseRedirect,
|
||||
)
|
||||
import emoji
|
||||
|
||||
|
||||
class TagUpdateView(PermissionRequiredMixin, SuccessMessageMixin, UpdateView):
|
||||
model = Tag
|
||||
permission_required = "agenda_culturel.change_tag"
|
||||
form_class = TagForm
|
||||
success_message = _("The tag has been successfully updated.")
|
||||
|
||||
|
||||
class TagCreateView(PermissionRequiredMixin, SuccessMessageMixin, CreateView):
|
||||
model = Tag
|
||||
permission_required = "agenda_culturel.add_tag"
|
||||
form_class = TagForm
|
||||
success_message = _("The tag has been successfully created.")
|
||||
|
||||
def get_initial(self, *args, **kwargs):
|
||||
initial = super().get_initial(**kwargs)
|
||||
if "name" in self.request.GET:
|
||||
initial["name"] = self.request.GET.get("name")
|
||||
return initial
|
||||
|
||||
def form_valid(self, form):
|
||||
Tag.clear_cache()
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class TagDeleteView(PermissionRequiredMixin, DeleteView):
|
||||
model = Tag
|
||||
permission_required = "agenda_culturel.delete_tag"
|
||||
success_url = reverse_lazy("view_all_tags")
|
||||
|
||||
|
||||
def view_tag_past(request, t):
|
||||
return view_tag(request, t, True)
|
||||
|
||||
|
||||
def view_tag(request, t, past=False):
|
||||
now = date.today()
|
||||
|
||||
qs = get_event_qs(request).filter(tags__contains=[t])
|
||||
|
||||
if past:
|
||||
qs = qs.filter(start_day__lt=now).order_by("-start_day", "-start_time")
|
||||
else:
|
||||
qs = qs.filter(start_day__gte=now).order_by("start_day", "start_time")
|
||||
|
||||
qs = qs.filter(
|
||||
Q(other_versions__isnull=True)
|
||||
| Q(other_versions__representative=F("pk"))
|
||||
| Q(other_versions__representative__isnull=True)
|
||||
)
|
||||
|
||||
paginator = Paginator(qs, 10)
|
||||
page = request.GET.get("page")
|
||||
|
||||
try:
|
||||
response = paginator.page(page)
|
||||
except PageNotAnInteger:
|
||||
response = paginator.page(1)
|
||||
except EmptyPage:
|
||||
response = paginator.page(paginator.num_pages)
|
||||
|
||||
rimports = RecurrentImport.objects.filter(defaultTags__contains=[t])
|
||||
|
||||
tag = Tag.objects.filter(name=t).first()
|
||||
context = {
|
||||
"tag": t,
|
||||
"paginator_filter": response,
|
||||
"object": tag,
|
||||
"rimports": rimports,
|
||||
"past": past,
|
||||
}
|
||||
return render(request, "agenda_culturel/tag.html", context)
|
||||
|
||||
|
||||
def tag_list(request):
|
||||
tags = Event.get_all_tags()
|
||||
r_tags = [t["tag"] for t in tags]
|
||||
objects = Tag.objects.order_by("name").all()
|
||||
d_objects = dict()
|
||||
for o in objects:
|
||||
d_objects[o.name] = o
|
||||
|
||||
tags = [
|
||||
t | {"obj": d_objects[t["tag"]]} if t["tag"] in d_objects else t for t in tags
|
||||
]
|
||||
tags += [
|
||||
{"obj": o, "tag": o.name, "count": 0} for o in objects if o.name not in r_tags
|
||||
]
|
||||
|
||||
context = {
|
||||
"tags": sorted(
|
||||
tags,
|
||||
key=lambda x: emoji.demojize(
|
||||
remove_accents(x["tag"]).lower(), delimiters=("000", "")
|
||||
),
|
||||
)
|
||||
}
|
||||
return render(request, "agenda_culturel/tags.html", context)
|
||||
|
||||
|
||||
@login_required(login_url="/accounts/login/")
|
||||
@permission_required("agenda_culturel.change_tag")
|
||||
def rename_tag(request, t):
|
||||
form = TagRenameForm(name=t)
|
||||
|
||||
if request.method == "POST":
|
||||
form = TagRenameForm(request.POST, name=t)
|
||||
if form.is_valid():
|
||||
save = True
|
||||
if form.cleaned_data["name"] == t:
|
||||
messages.warning(
|
||||
request,
|
||||
_("You have not modified the tag name."),
|
||||
)
|
||||
save = False
|
||||
elif not form.is_force():
|
||||
if (
|
||||
Event.objects.filter(
|
||||
tags__contains=[form.cleaned_data["name"]]
|
||||
).count()
|
||||
> 0
|
||||
):
|
||||
if Tag.objects.filter(name=form.cleaned_data["name"]):
|
||||
messages.warning(
|
||||
request,
|
||||
(
|
||||
_(
|
||||
"This tag {} is already in use, and is described by different information from the current tag. You can force renaming by checking the corresponding option. The information associated with tag {} will be deleted, and all events associated with tag {} will be associated with tag {}."
|
||||
)
|
||||
).format(
|
||||
form.cleaned_data["name"],
|
||||
t,
|
||||
t,
|
||||
form.cleaned_data["name"],
|
||||
),
|
||||
)
|
||||
else:
|
||||
messages.warning(
|
||||
request,
|
||||
(
|
||||
_(
|
||||
"This tag {} is already in use. You can force renaming by checking the corresponding option."
|
||||
)
|
||||
).format(form.cleaned_data["name"]),
|
||||
)
|
||||
save = False
|
||||
form = TagRenameForm(request.POST, name=t, force=True)
|
||||
|
||||
if save:
|
||||
# find all matching events and update them
|
||||
events = Event.objects.filter(tags__contains=[t])
|
||||
new_name = form.cleaned_data["name"]
|
||||
for e in events:
|
||||
e.tags = [te for te in e.tags if te != t]
|
||||
if new_name not in e.tags:
|
||||
e.tags += [new_name]
|
||||
Event.objects.bulk_update(events, fields=["tags"])
|
||||
|
||||
# find all recurrent imports and fix them
|
||||
rimports = RecurrentImport.objects.filter(defaultTags__contains=[t])
|
||||
for ri in rimports:
|
||||
ri.tags = [te for te in ri.defaultTags if te != t]
|
||||
if new_name not in ri.tags:
|
||||
ri.tags += [new_name]
|
||||
RecurrentImport.objects.bulk_update(rimports, fields=["defaultTags"])
|
||||
|
||||
# find tag object
|
||||
tag_object = Tag.objects.filter(name=t).first()
|
||||
if tag_object:
|
||||
tag_object.name = new_name
|
||||
tag_object.save()
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
(_("The tag {} has been successfully renamed to {}.")).format(
|
||||
t, form.cleaned_data["name"]
|
||||
),
|
||||
)
|
||||
return HttpResponseRedirect(
|
||||
reverse_lazy("view_tag", kwargs={"t": form.cleaned_data["name"]})
|
||||
)
|
||||
|
||||
nb = Event.objects.filter(tags__contains=[t]).count()
|
||||
|
||||
return render(
|
||||
request,
|
||||
"agenda_culturel/tag_rename_form.html",
|
||||
context={"form": form, "tag": t, "nb": nb},
|
||||
)
|
||||
|
||||
|
||||
@login_required(login_url="/accounts/login/")
|
||||
@permission_required("agenda_culturel.delete_tag")
|
||||
def delete_tag(request, t):
|
||||
respage = reverse_lazy("view_all_tags")
|
||||
|
||||
if request.method == "POST":
|
||||
# remove tag from events
|
||||
events = Event.objects.filter(tags__contains=[t])
|
||||
for e in events:
|
||||
e.tags = [te for te in e.tags if te != t]
|
||||
Event.objects.bulk_update(events, fields=["tags"])
|
||||
|
||||
# remove tag from recurrent imports
|
||||
rimports = RecurrentImport.objects.filter(defaultTags__contains=[t])
|
||||
for ri in rimports:
|
||||
ri.tags = [te for te in ri.defaultTags if te != t]
|
||||
RecurrentImport.objects.bulk_update(rimports, fields=["defaultTags"])
|
||||
|
||||
# find tag object
|
||||
tag_object = Tag.objects.filter(name=t).first()
|
||||
if tag_object:
|
||||
tag_object.delete()
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
(_("The tag {} has been successfully deleted.")).format(t),
|
||||
)
|
||||
return HttpResponseRedirect(respage)
|
||||
else:
|
||||
nb = Event.objects.filter(tags__contains=[t]).count()
|
||||
obj = Tag.objects.filter(name=t).first()
|
||||
nbi = RecurrentImport.objects.filter(defaultTags__contains=[t]).count()
|
||||
cancel_url = request.META.get("HTTP_REFERER", "")
|
||||
if cancel_url == "":
|
||||
cancel_url = respage
|
||||
return render(
|
||||
request,
|
||||
"agenda_culturel/tag_confirm_delete_by_name.html",
|
||||
{
|
||||
"tag": t,
|
||||
"nb": nb,
|
||||
"nbi": nbi,
|
||||
"cancel_url": cancel_url,
|
||||
"obj": obj,
|
||||
},
|
||||
)
|
19
src/agenda_culturel/views/utils.py
Normal file
19
src/agenda_culturel/views/utils.py
Normal file
@ -0,0 +1,19 @@
|
||||
from ..models import Event
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
#
|
||||
#
|
||||
# Useful for translation
|
||||
to_be_translated = [
|
||||
_("Recurrent import name"),
|
||||
_("Add another"),
|
||||
_("Browse..."),
|
||||
_("No file selected."),
|
||||
]
|
||||
|
||||
|
||||
def get_event_qs(request):
|
||||
if request.user.is_authenticated:
|
||||
return Event.objects.filter()
|
||||
else:
|
||||
return Event.objects.filter(status=Event.STATUS.PUBLISHED)
|
Loading…
x
Reference in New Issue
Block a user