more rapid merging/fixing
This commit is contained in:
@@ -0,0 +1,20 @@
|
|||||||
|
# Generated by Django 3.1.2 on 2021-01-18 13:39
|
||||||
|
|
||||||
|
import anatomy.models
|
||||||
|
import django.core.files.storage
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('anatomy', '0018_auto_20210112_1726'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='anatomyquestion',
|
||||||
|
name='image',
|
||||||
|
field=models.ImageField(storage=django.core.files.storage.FileSystemStorage(base_url='/media/anatomy/', location='/home/ross/sites/rad/media//anatomy/'), upload_to=anatomy.models.image_directory_path),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -32,6 +32,7 @@ ALLOWED_HOSTS = ["localhost", "127.0.0.1", "161.35.163.87", "penracourses.org.uk
|
|||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'anatomy',
|
'anatomy',
|
||||||
'physics',
|
'physics',
|
||||||
|
'rapids',
|
||||||
"reversion",
|
"reversion",
|
||||||
"django_tables2",
|
"django_tables2",
|
||||||
"django_filters",
|
"django_filters",
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from .models import Rapid, RapidImage, Examination, Site, Abnormality, Region, Note, RapidCreationDefault
|
from .models import Rapid, RapidImage, Examination, Site, Abnormality, Region, Note, RapidCreationDefault
|
||||||
import tagulous.admin
|
#import tagulous.admin
|
||||||
|
|
||||||
from django.forms import ModelForm
|
from django.forms import ModelForm
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ class RapidAdminForm(ModelForm):
|
|||||||
|
|
||||||
fields = [
|
fields = [
|
||||||
"normal", "abnormality", "region", "laterality", "examination",
|
"normal", "abnormality", "region", "laterality", "examination",
|
||||||
"condition", "sign", "site", "feedback", "author"
|
"site", "feedback", "author"
|
||||||
]
|
]
|
||||||
|
|
||||||
widgets = {
|
widgets = {
|
||||||
|
|||||||
+1
-1
@@ -82,7 +82,7 @@ class RapidForm(ModelForm):
|
|||||||
#fields = '__all__'
|
#fields = '__all__'
|
||||||
fields = [
|
fields = [
|
||||||
"normal", "abnormality", "region", "laterality", "examination",
|
"normal", "abnormality", "region", "laterality", "examination",
|
||||||
"condition", "sign", "site", "feedback"
|
"site", "feedback"
|
||||||
]
|
]
|
||||||
#fields = ['question', 'feedback', 'subspecialty', 'references']
|
#fields = ['question', 'feedback', 'subspecialty', 'references']
|
||||||
widgets = {
|
widgets = {
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
# Generated by Django 3.1.2 on 2021-01-18 13:39
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
import django.core.files.storage
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import rapids.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Abnormality',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(help_text='Primary abnormality on the film(s)', max_length=200, unique=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ('name',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Examination',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('examination', models.CharField(max_length=200)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ('examination',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Rapid',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('question', models.TextField(blank=True, null=True)),
|
||||||
|
('feedback', models.TextField(blank=True, null=True)),
|
||||||
|
('normal', models.BooleanField(default=False, help_text='Tick if true')),
|
||||||
|
('laterality', models.CharField(choices=[('NONE', 'None'), ('LEFT', 'Left'), ('RIGHT', 'Right'), ('BILAT', 'Bilateral')], default='NONE', help_text='Applies to the answer, not the examination', max_length=20)),
|
||||||
|
('verified', models.BooleanField(default=False)),
|
||||||
|
('created_date', models.DateTimeField(default=django.utils.timezone.now)),
|
||||||
|
('published_date', models.DateTimeField(blank=True, null=True)),
|
||||||
|
('scrapped', models.BooleanField(default=False, help_text='Question has been scrapped and will not be shown')),
|
||||||
|
('abnormality', models.ManyToManyField(blank=True, help_text='The abnormality (laterality and region independent)', to='rapids.Abnormality')),
|
||||||
|
('author', models.ManyToManyField(blank=True, help_text='Author of question', related_name='rapid_authored_questions', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('examination', models.ManyToManyField(help_text='Name of the (primary) examination', to='rapids.Examination')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Region',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(blank=True, help_text='Region of the abnormality (not including lateralitiy)', max_length=200, unique=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ('name',),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Site',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('site', models.CharField(max_length=200)),
|
||||||
|
('initials', models.CharField(max_length=200)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RapidImage',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('image', models.ImageField(storage=django.core.files.storage.FileSystemStorage(base_url='/media/rapids/', location='/home/ross/sites/rad/media//rapids/'), upload_to=rapids.models.image_directory_path)),
|
||||||
|
('feedback_image', models.BooleanField(default=False)),
|
||||||
|
('rapid', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='rapids.rapid')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='RapidCreationDefault',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('author', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='rapid_default', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('site', models.ManyToManyField(blank=True, default=1, help_text='Default site to use when creating a new rapid', to='rapids.Site')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rapid',
|
||||||
|
name='region',
|
||||||
|
field=models.ManyToManyField(blank=True, help_text='Region of the abnormality (laterality independent)', to='rapids.Region'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='rapid',
|
||||||
|
name='site',
|
||||||
|
field=models.ManyToManyField(blank=True, default=1, help_text='If we know the source of the image (should possibly be removed?)', to='rapids.Site'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Note',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('note', models.TextField()),
|
||||||
|
('created_on', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rapid_notes', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('rapid', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='rapid_notes', to='rapids.rapid')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Answer',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('answer', models.TextField(max_length=500)),
|
||||||
|
('answer_compare', models.TextField(max_length=500)),
|
||||||
|
('status', models.CharField(choices=[('', 'Unmarked'), ('0', 'Incorrect'), ('1', 'Half mark'), ('2', 'Correct')], default='', max_length=1)),
|
||||||
|
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='rapids.rapid')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
+67
-144
@@ -24,12 +24,6 @@ def image_directory_path(instance, filename):
|
|||||||
return u"picture/{0}".format(filename)
|
return u"picture/{0}".format(filename)
|
||||||
|
|
||||||
|
|
||||||
class AnswerStrings(models.Model):
|
|
||||||
text = models.CharField(max_length=255, unique=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.text
|
|
||||||
|
|
||||||
|
|
||||||
class Abnormality(models.Model):
|
class Abnormality(models.Model):
|
||||||
name = models.CharField(max_length=200,
|
name = models.CharField(max_length=200,
|
||||||
@@ -80,7 +74,7 @@ class Site(models.Model):
|
|||||||
|
|
||||||
class Answer(models.Model):
|
class Answer(models.Model):
|
||||||
question = models.ForeignKey(
|
question = models.ForeignKey(
|
||||||
Rapid, related_name="answers", on_delete=models.CASCADE
|
"Rapid", related_name="answers", on_delete=models.CASCADE
|
||||||
)
|
)
|
||||||
answer = models.TextField(max_length=500)
|
answer = models.TextField(max_length=500)
|
||||||
answer_compare = models.TextField(max_length=500)
|
answer_compare = models.TextField(max_length=500)
|
||||||
@@ -148,15 +142,15 @@ class Rapid(models.Model):
|
|||||||
choices=LATERALITY_CHOICES,
|
choices=LATERALITY_CHOICES,
|
||||||
default=NONE,
|
default=NONE,
|
||||||
help_text="Applies to the answer, not the examination")
|
help_text="Applies to the answer, not the examination")
|
||||||
condition = tagulous.models.TagField(
|
# condition = tagulous.models.TagField(
|
||||||
to=Condition,
|
# to=Condition,
|
||||||
blank=True,
|
# blank=True,
|
||||||
help_text=
|
# help_text=
|
||||||
"Associated condition. Will allow searching / filtering and tips / hints to be displayed. Conditions with spaces must be enclosed in quotes \"...\""
|
# "Associated condition. Will allow searching / filtering and tips / hints to be displayed. Conditions with spaces must be enclosed in quotes \"...\""
|
||||||
)
|
# )
|
||||||
|
#
|
||||||
sign = tagulous.models.TagField(
|
# sign = tagulous.models.TagField(
|
||||||
to=Sign, blank=True, help_text='Radiological signs in the question')
|
# to=Sign, blank=True, help_text='Radiological signs in the question')
|
||||||
|
|
||||||
DEFAULT_SITE_ID = 1
|
DEFAULT_SITE_ID = 1
|
||||||
site = models.ManyToManyField(
|
site = models.ManyToManyField(
|
||||||
@@ -206,134 +200,6 @@ class Rapid(models.Model):
|
|||||||
lat = self.laterality
|
lat = self.laterality
|
||||||
return "{} / {} {}".format(exams, lat, n)
|
return "{} / {} {}".format(exams, lat, n)
|
||||||
|
|
||||||
|
|
||||||
class RapidImage(models.Model):
|
|
||||||
rapid = models.ForeignKey(Rapid,
|
|
||||||
related_name="images",
|
|
||||||
on_delete=models.CASCADE)
|
|
||||||
image = models.ImageField(upload_to=image_directory_path, storage=image_storage)
|
|
||||||
|
|
||||||
feedback_image = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
def image_tag(self):
|
|
||||||
if self.image:
|
|
||||||
return mark_safe(
|
|
||||||
'<img src="{}{}" class="admin-rapid-image" /><span class="admin-rapid-image-info">Click and hold to zoom<span>'
|
|
||||||
.format(settings.MEDIA_URL, self.image))
|
|
||||||
else:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
image_tag.short_description = 'Image'
|
|
||||||
|
|
||||||
|
|
||||||
class Note(models.Model):
|
|
||||||
rapid = models.ForeignKey(Rapid,
|
|
||||||
related_name="rapid_notes",
|
|
||||||
on_delete=models.CASCADE,
|
|
||||||
null=True)
|
|
||||||
author = models.ForeignKey(User,
|
|
||||||
related_name="rapid_notes",
|
|
||||||
on_delete=models.CASCADE)
|
|
||||||
note = models.TextField()
|
|
||||||
created_on = models.DateTimeField(auto_now_add=True)
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
self.pk = self.rapid_id
|
|
||||||
return reverse('rapids:rapid_detail', kwargs={'pk': self.pk})
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return "{} [{}] {}".format(self.rapid, self.author, self.created_on)
|
|
||||||
|
|
||||||
|
|
||||||
class RapidCreationDefault(models.Model):
|
|
||||||
author = models.OneToOneField(User,
|
|
||||||
related_name="rapid_default",
|
|
||||||
on_delete=models.CASCADE)
|
|
||||||
|
|
||||||
site = models.ManyToManyField(
|
|
||||||
Site,
|
|
||||||
blank=True,
|
|
||||||
default=1,
|
|
||||||
help_text="Default site to use when creating a new rapid")
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse('rapids:rapid_create')
|
|
||||||
|
|
||||||
|
|
||||||
class AnatomyQuestion(models.Model):
|
|
||||||
# author = models.ForeignKey('auth.User', on_delete=models.CASCADE)
|
|
||||||
# image = models.ImageField()
|
|
||||||
# feedback = models.TextField(null=True)
|
|
||||||
question_type = models.ForeignKey(
|
|
||||||
QuestionType, on_delete=models.SET_NULL, null=True
|
|
||||||
)
|
|
||||||
|
|
||||||
image = models.ImageField(upload_to=image_directory_path, storage=image_storage)
|
|
||||||
|
|
||||||
image_annotations = models.TextField(
|
|
||||||
blank=True,
|
|
||||||
help_text="Stores a JSON representation of annotations to be applied by cornerstonetools",
|
|
||||||
)
|
|
||||||
|
|
||||||
description = models.CharField(
|
|
||||||
max_length=400,
|
|
||||||
help_text="Short description of the image e.g. 'Sagittal CT Chest, Abdomen & Pelvis', will be displayed as the title.",
|
|
||||||
)
|
|
||||||
|
|
||||||
examination = models.ForeignKey(
|
|
||||||
Examination, on_delete=models.SET_NULL, null=True, blank=True
|
|
||||||
)
|
|
||||||
modality = models.ForeignKey(
|
|
||||||
Modality,
|
|
||||||
on_delete=models.SET_NULL,
|
|
||||||
null=True,
|
|
||||||
help_text="Modality of the image",
|
|
||||||
)
|
|
||||||
region = models.ForeignKey(
|
|
||||||
Region,
|
|
||||||
on_delete=models.SET_NULL,
|
|
||||||
null=True,
|
|
||||||
blank=True,
|
|
||||||
help_text="Region the image covers",
|
|
||||||
)
|
|
||||||
body_part = models.ForeignKey(
|
|
||||||
BodyPart, on_delete=models.SET_NULL, null=True, blank=True
|
|
||||||
)
|
|
||||||
structure = models.ForeignKey(
|
|
||||||
Structure, on_delete=models.SET_NULL, null=True, blank=True
|
|
||||||
)
|
|
||||||
created_date = models.DateTimeField(default=timezone.now)
|
|
||||||
|
|
||||||
open_access = models.BooleanField(
|
|
||||||
help_text="If an question should be freely available to browse", default=True
|
|
||||||
)
|
|
||||||
|
|
||||||
author = models.ManyToManyField(
|
|
||||||
settings.AUTH_USER_MODEL,
|
|
||||||
blank=True,
|
|
||||||
help_text="Author(s) of question",
|
|
||||||
related_name="anatomy_authored_questions",
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
# Get first answer
|
|
||||||
return "{} [{}, {}]".format(
|
|
||||||
self.GetPrimaryAnswer(), self.modality, self.structure
|
|
||||||
)
|
|
||||||
# Get associated exams
|
|
||||||
e = self.exams.all().values_list("name", flat=True)
|
|
||||||
exams = ", ".join(e)
|
|
||||||
|
|
||||||
if a is None:
|
|
||||||
return "({})".format(exams)
|
|
||||||
else:
|
|
||||||
return "{} ({}) [{}, {}]".format(
|
|
||||||
a.answer, exams, self.modality, self.structure
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse("anatomy:question_detail", kwargs={"pk": self.pk})
|
|
||||||
|
|
||||||
def GetPrimaryAnswer(self):
|
def GetPrimaryAnswer(self):
|
||||||
if len(self.answers.all()) > 0:
|
if len(self.answers.all()) > 0:
|
||||||
return self.answers.all().first().answer
|
return self.answers.all().first().answer
|
||||||
@@ -387,3 +253,60 @@ class AnatomyQuestion(models.Model):
|
|||||||
|
|
||||||
marked_answers = correct_answers | half_mark_answers | incorrect_answers
|
marked_answers = correct_answers | half_mark_answers | incorrect_answers
|
||||||
return marked_answers
|
return marked_answers
|
||||||
|
|
||||||
|
|
||||||
|
class RapidImage(models.Model):
|
||||||
|
rapid = models.ForeignKey(Rapid,
|
||||||
|
related_name="images",
|
||||||
|
on_delete=models.CASCADE)
|
||||||
|
image = models.ImageField(upload_to=image_directory_path, storage=image_storage)
|
||||||
|
|
||||||
|
feedback_image = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
def image_tag(self):
|
||||||
|
if self.image:
|
||||||
|
return mark_safe(
|
||||||
|
'<img src="{}{}" class="admin-rapid-image" /><span class="admin-rapid-image-info">Click and hold to zoom<span>'
|
||||||
|
.format(settings.MEDIA_URL, self.image))
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
image_tag.short_description = 'Image'
|
||||||
|
|
||||||
|
|
||||||
|
class Note(models.Model):
|
||||||
|
rapid = models.ForeignKey(Rapid,
|
||||||
|
related_name="rapid_notes",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=True)
|
||||||
|
#author = models.ForeignKey(User,
|
||||||
|
author = models.ForeignKey( settings.AUTH_USER_MODEL,
|
||||||
|
related_name="rapid_notes",
|
||||||
|
on_delete=models.CASCADE)
|
||||||
|
note = models.TextField()
|
||||||
|
created_on = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
self.pk = self.rapid_id
|
||||||
|
return reverse('rapids:rapid_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{} [{}] {}".format(self.rapid, self.author, self.created_on)
|
||||||
|
|
||||||
|
|
||||||
|
class RapidCreationDefault(models.Model):
|
||||||
|
#author = models.OneToOneField(User,
|
||||||
|
author = models.OneToOneField(
|
||||||
|
settings.AUTH_USER_MODEL,
|
||||||
|
related_name="rapid_default",
|
||||||
|
on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
site = models.ManyToManyField(
|
||||||
|
Site,
|
||||||
|
blank=True,
|
||||||
|
default=1,
|
||||||
|
help_text="Default site to use when creating a new rapid")
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('rapids:rapid_create')
|
||||||
|
|
||||||
|
|||||||
@@ -9,10 +9,10 @@
|
|||||||
<link rel="stylesheet" href="{% static 'tagulous/lib/select2-3/select2.css' %}">
|
<link rel="stylesheet" href="{% static 'tagulous/lib/select2-3/select2.css' %}">
|
||||||
<link rel="stylesheet" href="{% static 'css/sbas.css' %}">
|
<link rel="stylesheet" href="{% static 'css/sbas.css' %}">
|
||||||
|
|
||||||
<script src="{% static 'tagulous/lib/jquery.js' %}"></script>
|
{% comment %} <script src="{% static 'tagulous/lib/jquery.js' %}"></script>
|
||||||
<script src="{% static 'tagulous/lib/select2-3/select2.min.js' %}"></script>
|
<script src="{% static 'tagulous/lib/select2-3/select2.min.js' %}"></script>
|
||||||
<script src="{% static 'tagulous/tagulous.js' %}"></script>
|
<script src="{% static 'tagulous/tagulous.js' %}"></script>
|
||||||
<script src="{% static 'tagulous/adaptor/select2-3.js' %}"></script>
|
<script src="{% static 'tagulous/adaptor/select2-3.js' %}"></script> {% endcomment %}
|
||||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
||||||
<script type="text/javascript" src="{% static 'js/dwv/i18next.min.js' %}"></script>
|
<script type="text/javascript" src="{% static 'js/dwv/i18next.min.js' %}"></script>
|
||||||
<script type="text/javascript" src="{% static 'js/dwv/dwv.min.js' %}"></script>
|
<script type="text/javascript" src="{% static 'js/dwv/dwv.min.js' %}"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user