fix a number of things
This commit is contained in:
+4
-2
@@ -583,8 +583,10 @@ class SelfReviewForm(ModelForm):
|
||||
widgets = {"user_exam": HiddenInput(), "case": HiddenInput()}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
user_exam_id = kwargs.pop("user_exam_id")
|
||||
case_id = kwargs.pop("case_id")
|
||||
if "user_exam_id" in kwargs:
|
||||
user_exam_id = kwargs.pop("user_exam_id")
|
||||
if "case_id" in kwargs:
|
||||
case_id = kwargs.pop("case_id")
|
||||
|
||||
ModelForm.__init__(self, *args, **kwargs)
|
||||
super(SelfReviewForm, self).__init__(*args, **kwargs)
|
||||
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 4.1.4 on 2023-08-07 08:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('atlas', '0007_selfreview'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='case',
|
||||
name='scrapped',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='case',
|
||||
name='archive',
|
||||
field=models.BooleanField(default=False, help_text='Case has been archived, this will remain on the system but will not be shown'),
|
||||
),
|
||||
]
|
||||
+120
-9
@@ -13,6 +13,7 @@ from django.utils import timezone
|
||||
|
||||
|
||||
import pydicom
|
||||
import pydicom.multival
|
||||
|
||||
from django.core.files.storage import FileSystemStorage
|
||||
from django.conf import settings
|
||||
@@ -272,8 +273,9 @@ class Case(models.Model):
|
||||
related_name="atlas_edited_cases",
|
||||
)
|
||||
|
||||
scrapped = models.BooleanField(
|
||||
default=False, help_text="Question has been scrapped and will not be shown"
|
||||
archive = models.BooleanField(
|
||||
default=False,
|
||||
help_text="Case has been archived, this will remain on the system but will not be shown",
|
||||
)
|
||||
|
||||
open_access = models.BooleanField(
|
||||
@@ -325,6 +327,30 @@ class Case(models.Model):
|
||||
examinations = [series.get_examination() for series in self.series.all()]
|
||||
return f"{self.pk}:{self.title} {'/'.join([str(c) for c in self.condition.all()])} [{', '.join(examinations)}]"
|
||||
|
||||
def get_case_dicom_json(self):
|
||||
series_json = []
|
||||
for series in self.series.all():
|
||||
series_json.append(series.get_series_dicom_json())
|
||||
|
||||
print(series_json)
|
||||
|
||||
return {
|
||||
"studies": [
|
||||
{
|
||||
"StudyInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.298806137288633453246975630178",
|
||||
"StudyDate": "20000101",
|
||||
"StudyTime": "",
|
||||
"PatientName": "",
|
||||
"PatientID": "LIDC-IDRI-0001",
|
||||
"AccessionNumber": "",
|
||||
"PatientAge": "",
|
||||
"PatientSex": "",
|
||||
"series": series_json,
|
||||
}
|
||||
]
|
||||
}
|
||||
pass
|
||||
|
||||
|
||||
class SeriesImage(SeriesImageBase):
|
||||
image = models.FileField(upload_to=image_directory_path)
|
||||
@@ -332,6 +358,50 @@ class SeriesImage(SeriesImageBase):
|
||||
"Series", related_name="images", on_delete=models.SET_NULL, null=True
|
||||
)
|
||||
|
||||
def get_image_dicom_json(self):
|
||||
try:
|
||||
with pydicom.dcmread(self.image) as ds:
|
||||
to_keep = [
|
||||
"Columns",
|
||||
"Rows",
|
||||
"InstanceNumber",
|
||||
"SOPClassUID",
|
||||
"PhotometricInterpretation",
|
||||
"BitsAllocated",
|
||||
"BitsStored",
|
||||
"PixelRepresentation",
|
||||
"SamplesPerPixel",
|
||||
"PixelSpacing",
|
||||
"HighBit",
|
||||
"ImageOrientationPatient",
|
||||
"ImagePositionPatient",
|
||||
"FrameOfReferenceUID",
|
||||
"ImageType",
|
||||
"Modality",
|
||||
"SOPInstanceUID",
|
||||
"SeriesInstanceUID",
|
||||
"StudyInstanceUID",
|
||||
"WindowCenter",
|
||||
"WindowWidth",
|
||||
"SeriesDate",
|
||||
]
|
||||
|
||||
d = {}
|
||||
|
||||
for key in to_keep:
|
||||
val = ds[key].value
|
||||
|
||||
if type(val) == pydicom.multival.MultiValue:
|
||||
d[key] = list(val)
|
||||
else:
|
||||
d[key] = val
|
||||
|
||||
|
||||
|
||||
return {"metadata": d, "url": self.image.url}
|
||||
except FileNotFoundError:
|
||||
return []
|
||||
|
||||
|
||||
class SeriesFinding(models.Model):
|
||||
series = models.ForeignKey(
|
||||
@@ -411,6 +481,23 @@ class Series(SeriesBase):
|
||||
"<a href='{}'>{}</a>", self.get_absolute_url(), self.get_full_str()
|
||||
)
|
||||
|
||||
def get_series_dicom_json(self):
|
||||
instances = []
|
||||
|
||||
image: SeriesImage
|
||||
for image in self.images.all():
|
||||
instances.append(image.get_image_dicom_json())
|
||||
|
||||
series_json = {
|
||||
"SeriesInstanceUID": "1.3.6.1.4.1.14519.5.2.1.6279.6001.179049373636438705059720603192",
|
||||
"SeriesNumber": 3000566,
|
||||
"Modality": "CT",
|
||||
"SliceThickness": 2.5,
|
||||
"instances": instances,
|
||||
}
|
||||
|
||||
return series_json
|
||||
|
||||
|
||||
class CaseCollection(ExamCollectionGenericBase):
|
||||
app_name = "atlas"
|
||||
@@ -522,10 +609,14 @@ class CaseCollection(ExamCollectionGenericBase):
|
||||
|
||||
return False
|
||||
|
||||
def get_case_by_index(self, case_index:int, case_count=False) -> Case | Tuple[Case, int]:
|
||||
def get_case_by_index(
|
||||
self, case_index: int, case_count=False
|
||||
) -> Case | Tuple[Case, int]:
|
||||
# Seems to be a bug when case_number is 0 or 1 the same (first) object gets returned
|
||||
# forces a list fixes (but is likely inefficient)
|
||||
cases = list(self.cases.all().order_by("casedetail__sort_order").prefetch_related())
|
||||
cases = list(
|
||||
self.cases.all().order_by("casedetail__sort_order").prefetch_related()
|
||||
)
|
||||
|
||||
try:
|
||||
case = cases[case_index]
|
||||
@@ -534,10 +625,20 @@ class CaseCollection(ExamCollectionGenericBase):
|
||||
raise Http404(s)
|
||||
|
||||
if case_count:
|
||||
return case, len(cases)
|
||||
return case, len(cases)
|
||||
else:
|
||||
return case
|
||||
|
||||
def get_case_take_url(self, case):
|
||||
cases = list(
|
||||
self.cases.all().order_by("casedetail__sort_order").prefetch_related()
|
||||
)
|
||||
|
||||
return reverse(
|
||||
"atlas:collection_case_view_take_user",
|
||||
kwargs={"pk": self.pk, "case_number": cases.index(case)},
|
||||
)
|
||||
|
||||
|
||||
class CaseDetail(models.Model):
|
||||
case = models.ForeignKey(Case, on_delete=models.CASCADE)
|
||||
@@ -617,9 +718,7 @@ class SelfReview(models.Model):
|
||||
review_update_date = models.DateTimeField(auto_now=True)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse(
|
||||
"atlas:collection_take_overview_user", kwargs={"pk": self.user_exam.exam.pk}
|
||||
)
|
||||
return self.user_exam.exam.get_case_take_url(self.case)
|
||||
|
||||
def get_display_block(self):
|
||||
html = """<div class='self-feedback-block'>
|
||||
@@ -628,15 +727,27 @@ class SelfReview(models.Model):
|
||||
Findings: <span class="findings">{findings}</span><br/>
|
||||
|
||||
Interpretation: <span class="interpretation">{interpretation}</span><br/>
|
||||
<span class="date">Date: {date}</span><br/>
|
||||
<span class="date">Date: {date}</span><br/><a href="{edit_url}">Edit</a>
|
||||
</div>
|
||||
|
||||
"""
|
||||
|
||||
edit_url = reverse(
|
||||
"atlas:self_review_update",
|
||||
kwargs={
|
||||
"user_exam_id": self.user_exam.pk,
|
||||
"case_id": self.case.pk,
|
||||
"pk": self.pk,
|
||||
},
|
||||
)
|
||||
# edit_url = "TEST"
|
||||
|
||||
return format_html(
|
||||
html,
|
||||
comments=self.comments,
|
||||
findings=self.findings,
|
||||
interpretation=self.interpretation,
|
||||
date=self.review_date,
|
||||
id=self.pk,
|
||||
edit_url=edit_url,
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{% extends 'atlas/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<span class="collection-name-blend">Collection: {{collection}}</span>
|
||||
<h2>Case {{case_number|add:1}}
|
||||
|
||||
{% if show_title %}
|
||||
@@ -75,6 +76,10 @@
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if collection.publish_results or cid_user_exam.completed %}
|
||||
<a href="{% url 'atlas:add_self_review' cid_user_exam.id case.id %}"><button>Add self review</button></a>
|
||||
|
||||
{% endif %}
|
||||
<form method="POST" class="post-form">{% csrf_token %}
|
||||
{% if collection.collection_type == "REP" %}
|
||||
<div class="form-contents">
|
||||
@@ -84,21 +89,22 @@
|
||||
</div>
|
||||
<p>
|
||||
{% if completed %}
|
||||
<h4>Answer score: {{answer.score}}</h4>
|
||||
Answer feedback: {{answer.feedback|safe}}
|
||||
<br/>
|
||||
|
||||
{% if self_review %}
|
||||
<h4>Self Feedback</h4>
|
||||
{% if collection.self_review %}
|
||||
{% if self_review %}
|
||||
<h4>Self Feedback</h4>
|
||||
|
||||
{% for review in self_review %}
|
||||
{{review.get_display_block}}
|
||||
{% for review in self_review %}
|
||||
{{review.get_display_block}}
|
||||
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<h4>Answer score: {{answer.score}}</h4>
|
||||
Answer feedback: {{answer.feedback|safe}}
|
||||
<br/>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
@@ -117,10 +123,6 @@
|
||||
<br />
|
||||
<button type="submit" name="finish" class="save btn btn-default">Overview</button>
|
||||
<button type="submit" id="goto-button" value="test" name="goto" class="hide">goto</button>
|
||||
{% if collection.publish_results or cid_user_exam.completed %}
|
||||
<a href="{% url 'atlas:add_self_review' cid_user_exam.id case.id %}">Add self review</a>
|
||||
|
||||
{% endif %}
|
||||
</form>
|
||||
<style>
|
||||
label {
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
{{answer_count}} out of {{collection_length}} cases answered. Click to go to case.
|
||||
<div class="sba-finish-list">
|
||||
<ul>
|
||||
{% for question, answer in question_answer_tuples %}
|
||||
<li> <span {% if not collection.review_only and not answer %}class="unanswered"{% endif %}>
|
||||
{% for question, answer, self_review in question_answer_tuples %}
|
||||
<li> <span class="{% if not collection.review_only and not answer %}unanswered{% endif %}">
|
||||
<a href="
|
||||
{% if cid is not None %}
|
||||
{% url 'atlas:collection_case_view_take' pk=collection.id case_number=forloop.counter0 cid=cid passcode=passcode %}
|
||||
@@ -24,7 +24,7 @@
|
||||
{% url 'atlas:collection_case_view_take_user' pk=collection.id case_number=forloop.counter0 %}
|
||||
{% endif %}
|
||||
">
|
||||
Case: {{forloop.counter}}
|
||||
Case: {{forloop.counter}}{% if self_review %} <span class="self-review-complete" title="You have reviewed this case."> <i class="bi-card-checklist"></i></span>{% endif %}
|
||||
</a>
|
||||
<br/>
|
||||
{% if not collection.review_only %}
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
<button>Start</button>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% if cid_exam.completed %}
|
||||
<a href="{% url 'atlas:collection_take_overview_user' pk=collection.pk %}"><button>Overview</button></a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
Enter your CID and passcode in the below boxes.<br />
|
||||
<p><input id="cid-box" type="text" value="Candidate ID"></p>
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
{% extends 'atlas/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Atlas</h2>
|
||||
|
||||
<a href="{% url 'atlas:user_collections' %}">Available collections</a>
|
||||
|
||||
{% endblock %}
|
||||
@@ -33,4 +33,5 @@
|
||||
</div> {% endcomment %}
|
||||
<input type="submit" value="Submit">
|
||||
</form>
|
||||
{% if review %}<br/><a href="{% url 'atlas:self_review_delete' user_exam.id case.id review.id %}"><button>Delete review</button></a>{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{% extends 'atlas/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Collections</h2>
|
||||
|
||||
|
||||
<ul>
|
||||
{% for collection in collections %}
|
||||
<li>
|
||||
<a href="{% url 'atlas:collection_take_start' collection.pk %}">{{collection}} {{collelction.is_complete}}</a>
|
||||
</li>
|
||||
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{% endblock %}
|
||||
+19
-2
@@ -8,21 +8,32 @@ from . import views
|
||||
app_name = "atlas"
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.CaseView.as_view(), name="index"),
|
||||
path("", views.index, name="index"),
|
||||
path(
|
||||
"create/",
|
||||
RedirectView.as_view(pattern_name="atlas:case_create", permanent=False),
|
||||
name="create",
|
||||
),
|
||||
path(
|
||||
"add_self_review/<int:user_exam_id>/<int:case_id>",
|
||||
"self_review/<int:user_exam_id>/<int:case_id>/add",
|
||||
views.AddSelfReview.as_view(),
|
||||
name="add_self_review",
|
||||
),
|
||||
path(
|
||||
"self_review/<int:user_exam_id>/<int:case_id>/update/<int:pk>",
|
||||
views.SelfReviewUpdate.as_view(),
|
||||
name="self_review_update",
|
||||
),
|
||||
path(
|
||||
"self_review/<int:user_exam_id>/<int:case_id>/delete/<int:pk>",
|
||||
views.SelfReviewDelete.as_view(),
|
||||
name="self_review_delete",
|
||||
),
|
||||
path("author/<int:pk>/", views.author_detail, name="author_detail"),
|
||||
path("author/", views.author_list, name="author_list"),
|
||||
path("case/", views.CaseView.as_view(), name="case_view"),
|
||||
path("collection/", views.CollectionView.as_view(), name="collection_view"),
|
||||
path("collection/user", views.user_collections, name="user_collections"),
|
||||
path(
|
||||
"collection/create",
|
||||
views.CaseCollectionCreate.as_view(),
|
||||
@@ -125,6 +136,12 @@ urlpatterns = [
|
||||
views.GenericExamViews.exam_json_edit,
|
||||
name="exam_json_edit",
|
||||
),
|
||||
path(
|
||||
"case/<int:pk>/dicom_json",
|
||||
views.case_dicom_json,
|
||||
name="case_dicom_json",
|
||||
),
|
||||
|
||||
path("condition/", views.ConditionView.as_view(), name="condition_view"),
|
||||
path("categories/", views.categories_list, name="categories_list"),
|
||||
path("condition/<int:pk>", views.condition_detail, name="condition_detail"),
|
||||
|
||||
+85
-4
@@ -290,6 +290,13 @@ def author_list(request):
|
||||
|
||||
return render(request, "atlas/author_list.html", {"authors": authors})
|
||||
|
||||
def index(request):
|
||||
return render(request, "atlas/index.html", {})
|
||||
|
||||
def user_collections(request):
|
||||
collections = request.user.user_casecollection_exams.all()
|
||||
|
||||
return render(request, "atlas/user_collections.html", {"collections": collections})
|
||||
|
||||
class CaseDelete(RevisionMixin, AuthorOrCheckerRequiredMixin, DeleteView):
|
||||
model = Case
|
||||
@@ -1325,12 +1332,13 @@ def collection_take_overview(
|
||||
|
||||
question_answer_tuples = []
|
||||
answer_count = 0
|
||||
for q in case_details:
|
||||
if q in answer_question_map and answer_question_map[q].answer:
|
||||
question_answer_tuples.append((q, answer_question_map[q]))
|
||||
for cd in case_details:
|
||||
self_review = cid_user_exam.selfreview_set.filter(case=cd.case)
|
||||
if cd in answer_question_map and answer_question_map[cd].answer:
|
||||
question_answer_tuples.append((cd, answer_question_map[cd], self_review))
|
||||
answer_count += 1
|
||||
else:
|
||||
question_answer_tuples.append((q, None))
|
||||
question_answer_tuples.append((cd, None, self_review))
|
||||
|
||||
return render(
|
||||
request,
|
||||
@@ -1347,6 +1355,7 @@ def collection_take_overview(
|
||||
)
|
||||
|
||||
|
||||
|
||||
def collection_case_view_take_user(request, pk: int, case_number: int):
|
||||
return collection_case_view_take(request, pk, case_number)
|
||||
|
||||
@@ -1543,6 +1552,10 @@ def collection_case_view(request, pk, case_number):
|
||||
},
|
||||
)
|
||||
|
||||
def case_dicom_json(request, pk):
|
||||
case = get_object_or_404(Case, pk=pk)
|
||||
|
||||
return JsonResponse(case.get_case_dicom_json())
|
||||
|
||||
@user_is_collection_author_or_atlas_editor
|
||||
def delete_collection_cid_answers(request, exam_id, cid):
|
||||
@@ -1761,6 +1774,70 @@ class CollectionCloneMixin:
|
||||
class CollectionClone(CollectionCloneMixin, CaseCollectionCreate):
|
||||
"""Clone exam view"""
|
||||
|
||||
class SelfReviewDelete(DeleteView):
|
||||
model = SelfReview
|
||||
template_name = "confirm_delete.html"
|
||||
#success_url = reverse_lazy("atlas:finding_view")
|
||||
|
||||
def get_initial(self):
|
||||
user_exam_id = self.kwargs["user_exam_id"]
|
||||
case_id = self.kwargs["case_id"]
|
||||
review_id = self.kwargs["pk"]
|
||||
|
||||
self.user_exam = get_object_or_404(CidUserExam, pk=user_exam_id)
|
||||
self.case = get_object_or_404(Case, pk=case_id)
|
||||
self.review = get_object_or_404(SelfReview, pk=review_id)
|
||||
return {"user_exam": user_exam_id, "case": case_id, "review": review_id}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
# This can also be accessed via view.**** in the template
|
||||
context = super(SelfReviewDelete, self).get_context_data(**kwargs)
|
||||
context["user_exam"] = self.user_exam
|
||||
context["case"] = self.case
|
||||
context["review"] = self.review
|
||||
return context
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return self.user_exam.exam.get_case_take_url(self.case)
|
||||
|
||||
class SelfReviewUpdate(
|
||||
UpdateView
|
||||
):
|
||||
model = SelfReview
|
||||
form_class = SelfReviewForm
|
||||
|
||||
def get_initial(self):
|
||||
user_exam_id = self.kwargs["user_exam_id"]
|
||||
case_id = self.kwargs["case_id"]
|
||||
review_id = self.kwargs["pk"]
|
||||
|
||||
self.user_exam = get_object_or_404(CidUserExam, pk=user_exam_id)
|
||||
self.case = get_object_or_404(Case, pk=case_id)
|
||||
self.review = get_object_or_404(SelfReview, pk=review_id)
|
||||
return {"user_exam": user_exam_id, "case": case_id, "review": review_id}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
# This can also be accessed via view.**** in the template
|
||||
context = super(SelfReviewUpdate, self).get_context_data(**kwargs)
|
||||
context["user_exam"] = self.user_exam
|
||||
context["case"] = self.case
|
||||
context["review"] = self.review
|
||||
return context
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return self.user_exam.exam.get_case_take_url(self.case)
|
||||
|
||||
#def get_object(self):
|
||||
# return self.review
|
||||
# return
|
||||
#def get_initial(self):
|
||||
# user_exam_id = self.kwargs["user_exam_id"]
|
||||
# case_id = self.kwargs["case_id"]
|
||||
|
||||
# self.user_exam = get_object_or_404(CidUserExam, pk=user_exam_id)
|
||||
# self.case = get_object_or_404(Case, pk=case_id)
|
||||
# return {"user_exam": user_exam_id, "case": case_id}
|
||||
|
||||
class AddSelfReview(CreateView):
|
||||
model = SelfReview
|
||||
form_class = SelfReviewForm
|
||||
@@ -1789,6 +1866,10 @@ class AddSelfReview(CreateView):
|
||||
context["case"] = self.case
|
||||
return context
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return self.user_exam.exam.get_case_take_url(self.case)
|
||||
#return reverse("atlas:collection_take_overview_user", kwargs={"pk": self.user_exam.collection.id })
|
||||
|
||||
# def form_valid(self, form):
|
||||
# model = form.save(commit=False)
|
||||
#
|
||||
|
||||
@@ -8,6 +8,7 @@ services:
|
||||
volumes:
|
||||
- ./rad/:/usr/src/rad/
|
||||
- ../rts/:/usr/src/rad/rts
|
||||
- ../Viewers/platform/app/dist/:/usr/src/rad/viewer
|
||||
- ./backups:/usr/src/backups
|
||||
- ./media:/usr/src/media
|
||||
- ./static:/usr/src/static
|
||||
|
||||
@@ -1013,4 +1013,9 @@ h1, h2, h3, h4, h5, h6 {
|
||||
.self-feedback-block.data {
|
||||
opacity: 60%;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.collection-name-blend {
|
||||
opacity: 70%;
|
||||
font-size: small;
|
||||
}
|
||||
@@ -164,6 +164,7 @@ if settings.DEBUG:
|
||||
#urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
urlpatterns += static("/rts/", document_root="rts")
|
||||
urlpatterns += static("/viewer/", document_root="viewer")
|
||||
import debug_toolbar
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
<link href="{% static 'css/widgets.css' %}" type="text/css" media="all" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{% static 'css/dicomViewer.css' %}">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.css" integrity="sha512-6S2HWzVFxruDlZxI3sXOZZ4/eJ8AcxkQH1+JjSe/ONCEqR9L4Ysq5JdT5ipqtzU7WHalNwzwBv+iE51gNHJNqQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
|
||||
|
||||
|
||||
<link href="/static/css/widgets.css" type="text/css" media="all" rel="stylesheet">
|
||||
<link href="/static/admin/css/vendor/select2/select2.min.css" type="text/css" media="screen" rel="stylesheet">
|
||||
|
||||
Reference in New Issue
Block a user