From 1e55d195e94e4736fc963b0b6d4edfa453ed8f99 Mon Sep 17 00:00:00 2001 From: Ross Date: Mon, 7 Oct 2024 17:10:39 +0100 Subject: [PATCH] start oef --- oef/__init__.py | 0 oef/admin.py | 9 +++ oef/apps.py | 6 ++ oef/filters.py | 18 +++++ oef/management/__init__.py | 0 oef/management/commands/__init__.py | 0 oef/management/commands/importoef.py | 70 +++++++++++++++++++ oef/migrations/0001_initial.py | 36 ++++++++++ ...002_entry_oef_name_alter_entry_comments.py | 23 ++++++ oef/migrations/0003_alter_formats_format.py | 18 +++++ oef/migrations/__init__.py | 0 oef/models.py | 61 ++++++++++++++++ oef/tables.py | 35 ++++++++++ oef/templates/oef/base.html | 9 +++ oef/templates/oef/entry_table.html | 25 +++++++ oef/templates/oef/entry_update_view.html | 8 +++ oef/templates/oef/formats_apply.html | 33 +++++++++ oef/templates/oef/formats_update_form.html | 8 +++ oef/templates/oef/formats_view.html | 22 ++++++ oef/tests.py | 3 + oef/urls.py | 33 +++++++++ oef/views.py | 49 +++++++++++++ rad/settings.py | 1 + rad/urls.py | 1 + 24 files changed, 468 insertions(+) create mode 100644 oef/__init__.py create mode 100644 oef/admin.py create mode 100644 oef/apps.py create mode 100644 oef/filters.py create mode 100644 oef/management/__init__.py create mode 100644 oef/management/commands/__init__.py create mode 100644 oef/management/commands/importoef.py create mode 100644 oef/migrations/0001_initial.py create mode 100644 oef/migrations/0002_entry_oef_name_alter_entry_comments.py create mode 100644 oef/migrations/0003_alter_formats_format.py create mode 100644 oef/migrations/__init__.py create mode 100644 oef/models.py create mode 100755 oef/tables.py create mode 100644 oef/templates/oef/base.html create mode 100644 oef/templates/oef/entry_table.html create mode 100644 oef/templates/oef/entry_update_view.html create mode 100644 oef/templates/oef/formats_apply.html create mode 100644 oef/templates/oef/formats_update_form.html create mode 100644 oef/templates/oef/formats_view.html create mode 100644 oef/tests.py create mode 100644 oef/urls.py create mode 100644 oef/views.py diff --git a/oef/__init__.py b/oef/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/oef/admin.py b/oef/admin.py new file mode 100644 index 00000000..f1c11913 --- /dev/null +++ b/oef/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin + +# Register your models here. + +from .models import Entry, Formats + + +admin.site.register(Entry) +admin.site.register(Formats) diff --git a/oef/apps.py b/oef/apps.py new file mode 100644 index 00000000..46757cba --- /dev/null +++ b/oef/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class OefConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'oef' diff --git a/oef/filters.py b/oef/filters.py new file mode 100644 index 00000000..a227d78d --- /dev/null +++ b/oef/filters.py @@ -0,0 +1,18 @@ + +import django_filters + +from .models import Entry +from django.contrib.auth.models import User + +from django.db.models import Q + +class EntryFilter(django_filters.FilterSet): + class Meta: + model = Entry + fields = { + "modality": ["icontains"], + "exam_code": ["icontains"], + "exam_name": ["icontains"], + "questions": ["icontains"], + "comments": ["icontains"], + } \ No newline at end of file diff --git a/oef/management/__init__.py b/oef/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/oef/management/commands/__init__.py b/oef/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/oef/management/commands/importoef.py b/oef/management/commands/importoef.py new file mode 100644 index 00000000..f610a37d --- /dev/null +++ b/oef/management/commands/importoef.py @@ -0,0 +1,70 @@ + +from pathlib import Path +from django.core.management.base import BaseCommand, CommandError +import argparse + +from oef.models import Entry +from bs4 import BeautifulSoup + +class Command(BaseCommand): + help = "Imports the RCR spreadsheet" + + def add_arguments(self, parser): + # parser.add_argument("poll_ids", nargs="+", type=int) + parser.add_argument('--file', type=argparse.FileType('r')) + + + + def handle(self, *args, **options): + #with open(options["file"]) as f: + soup = BeautifulSoup(options["file"], "html.parser") + + Entry.objects.all().delete() + rows = 0 + for row in soup.find_all("tr"): + if rows > 0: + entry.questions = entry.questions + "\n" + row.find_all("td")[0].text + rows = rows - 1 + if rows == 0: + entry.save() + continue + + cells = row.find_all("td") + + if cells[0].text == "Modality": + continue + + + entry = Entry() + entry.modality = cells[0].text + entry.exam_code = cells[1].text + entry.exam_name = cells[2].text + entry.questions = cells[3].text + entry.oef_name = cells[4].text + entry.comments = cells[5].text + + if cells[0].has_attr("rowspan"): + rows = int(cells[0]["rowspan"]) - 1 + else: + entry.save() + + #Entry.objects.all().delete() + + #for row in soup.find_all("tr"): + # cells = row.find_all("td") + # if len(cells) == 6: + # entry = Entry() + # entry.modality = cells[0].text + # entry.exam_code = cells[1].text + # entry.exam_name = cells[2].text + # entry.questions = cells[3].text + # #entry. + # entry.comments = cells[5].text + # entry.save() + # self.stdout.write( + # self.style.SUCCESS(f"Entry added: {entry.pk}/{entry.exam_name}") + # ) + # else: + # self.stdout.write( + # self.style.WARNING(f"Skipping row: {row}") + # ) \ No newline at end of file diff --git a/oef/migrations/0001_initial.py b/oef/migrations/0001_initial.py new file mode 100644 index 00000000..c66f41b9 --- /dev/null +++ b/oef/migrations/0001_initial.py @@ -0,0 +1,36 @@ +# Generated by Django 5.0.2 on 2024-10-07 13:30 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Entry', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('modality', models.CharField(max_length=200)), + ('exam_code', models.CharField(max_length=50)), + ('exam_name', models.CharField(max_length=200)), + ('questions', models.TextField()), + ('questions_original', models.TextField()), + ('comments', models.TextField()), + ], + ), + migrations.CreateModel( + name='Formats', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200)), + ('format', models.TextField()), + ('inherits', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='oef.formats')), + ], + ), + ] diff --git a/oef/migrations/0002_entry_oef_name_alter_entry_comments.py b/oef/migrations/0002_entry_oef_name_alter_entry_comments.py new file mode 100644 index 00000000..1349b54b --- /dev/null +++ b/oef/migrations/0002_entry_oef_name_alter_entry_comments.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.2 on 2024-10-07 15:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('oef', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='entry', + name='oef_name', + field=models.CharField(blank=True, max_length=200), + ), + migrations.AlterField( + model_name='entry', + name='comments', + field=models.TextField(blank=True), + ), + ] diff --git a/oef/migrations/0003_alter_formats_format.py b/oef/migrations/0003_alter_formats_format.py new file mode 100644 index 00000000..9dcf34d5 --- /dev/null +++ b/oef/migrations/0003_alter_formats_format.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.2 on 2024-10-07 15:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('oef', '0002_entry_oef_name_alter_entry_comments'), + ] + + operations = [ + migrations.AlterField( + model_name='formats', + name='format', + field=models.TextField(blank=True), + ), + ] diff --git a/oef/migrations/__init__.py b/oef/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/oef/models.py b/oef/models.py new file mode 100644 index 00000000..5c69a43c --- /dev/null +++ b/oef/models.py @@ -0,0 +1,61 @@ +from django.db import models +from django.urls import reverse + +# Create your models here. +class Entry(models.Model): + modality = models.CharField(max_length=200) + exam_code = models.CharField(max_length=50) + exam_name = models.CharField(max_length=200) + + questions = models.TextField() + + questions_original = models.TextField() + + oef_name = models.CharField(max_length=200, blank=True) + + comments = models.TextField(blank=True) + + #format = models.ForeignKey('Formats', on_delete=models.CASCADE, null=True, blank=True) + + def __str__(self): + return self.exam_name + + def get_absolute_url(self): + return reverse("oef:entry_update", kwargs={"pk": self.pk}) + + def clean(self) -> None: + self.questions = self.questions.replace("\r\n", "\n") + return super().clean() + + def save(self, *args, **kwargs): + self.questions = self.questions.replace("\r\n", "\n") + return super().save(*args, **kwargs) + +class Formats(models.Model): + name = models.CharField(max_length=200) + format = models.TextField(blank=True) + + inherits = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True) + + def __str__(self): + return self.name + + def get_absolute_url(self): + return reverse("oef:format_update", kwargs={"pk": self.pk}) + + def clean(self) -> None: + self.format = self.format.replace("\r\n", "\n") + return super().clean() + + def save(self, *args, **kwargs): + self.format = self.format.replace("\r\n", "\n") + return super().save(*args, **kwargs) + + def get_format(self): + formats = [self.format] + while self.inherits: + self = self.inherits + formats.append(self.format) + + formats.reverse() + return "\n".join(formats) \ No newline at end of file diff --git a/oef/tables.py b/oef/tables.py new file mode 100755 index 00000000..bee166cc --- /dev/null +++ b/oef/tables.py @@ -0,0 +1,35 @@ +import django_tables2 as tables +from django_tables2.utils import A + +from generic.tables import SeriesImageColumn + +from .models import ( + Entry, + Formats +) + +from django.utils.html import format_html + + + + + +class EntryTable(tables.Table): + edit = tables.LinkColumn( + "oef:entry_update", text="Edit", args=[A("pk")], orderable=False + ) + + class Meta: + model = Entry + template_name = "django_tables2/bootstrap4.html" + fields = ( + "modality", + "exam_code", + "exam_name", + "questions", + "comments", + ) + + + def render_questions(self, value): + return format_html("
{}
", value) diff --git a/oef/templates/oef/base.html b/oef/templates/oef/base.html new file mode 100644 index 00000000..96f909ec --- /dev/null +++ b/oef/templates/oef/base.html @@ -0,0 +1,9 @@ +{% extends 'base.html' %} + +{% block css %} +{% endblock %} + +{% block navigation %} + Entries / + Formats / +{% endblock %} \ No newline at end of file diff --git a/oef/templates/oef/entry_table.html b/oef/templates/oef/entry_table.html new file mode 100644 index 00000000..a30707ef --- /dev/null +++ b/oef/templates/oef/entry_table.html @@ -0,0 +1,25 @@ +{% extends 'oef/base.html' %} + +{% block css %} +{% endblock %} + +{% block content %} + +
+
+

Filter Entries

+
+
+ + {{ filter.form }} + + +
+
+
+ {% autoescape off %} +{% load render_table from django_tables2 %} +{% render_table table %} + {% endautoescape %} + +{% endblock %} \ No newline at end of file diff --git a/oef/templates/oef/entry_update_view.html b/oef/templates/oef/entry_update_view.html new file mode 100644 index 00000000..2dcc4a17 --- /dev/null +++ b/oef/templates/oef/entry_update_view.html @@ -0,0 +1,8 @@ +{% extends 'oef/base.html' %} + +{% block content %} +
{% csrf_token %} + {{ form }} + +
+{% endblock content %} \ No newline at end of file diff --git a/oef/templates/oef/formats_apply.html b/oef/templates/oef/formats_apply.html new file mode 100644 index 00000000..5eccb562 --- /dev/null +++ b/oef/templates/oef/formats_apply.html @@ -0,0 +1,33 @@ + +{% extends 'oef/base.html' %} + + + +{% block content %} +

Formats Search

+ +The format {{format.name}} exists in the following {{ entries.count }} entries: + +
+{{ format.get_format}}
+
+ + + + +{% endblock content %} + + +{% block css %} + +{% endblock %} \ No newline at end of file diff --git a/oef/templates/oef/formats_update_form.html b/oef/templates/oef/formats_update_form.html new file mode 100644 index 00000000..2dcc4a17 --- /dev/null +++ b/oef/templates/oef/formats_update_form.html @@ -0,0 +1,8 @@ +{% extends 'oef/base.html' %} + +{% block content %} +
{% csrf_token %} + {{ form }} + +
+{% endblock content %} \ No newline at end of file diff --git a/oef/templates/oef/formats_view.html b/oef/templates/oef/formats_view.html new file mode 100644 index 00000000..8335425a --- /dev/null +++ b/oef/templates/oef/formats_view.html @@ -0,0 +1,22 @@ + +{% extends 'oef/base.html' %} + + + +{% block content %} +

Formats

+Create a new format + + + + +{% endblock content %} + \ No newline at end of file diff --git a/oef/tests.py b/oef/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/oef/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/oef/urls.py b/oef/urls.py new file mode 100644 index 00000000..d9f4fa90 --- /dev/null +++ b/oef/urls.py @@ -0,0 +1,33 @@ + +from django.contrib import admin +from django.urls import path, include +from django.conf import settings +from django.conf.urls.static import static +from django.views.generic import TemplateView + + +from . import views + +app_name = "oef" + +urlpatterns = [ + path( + "entries", views.EntryTableView.as_view(), name="entry_table_view" + ), + path( + "formats", views.formats_view, name="formats_view" + ), + path( + "formats/create", views.FormatsCreateView.as_view(), name="format_create" + ), + path( + "entries//update", views.EntryUpdateView.as_view(), name="entry_update" + ), + path( + "formats//update", views.FormatsUpdateView.as_view(), name="format_update" + ), + path( + "formats//apply", views.formats_apply, name="formats_apply" + ), + +] \ No newline at end of file diff --git a/oef/views.py b/oef/views.py new file mode 100644 index 00000000..5aca606e --- /dev/null +++ b/oef/views.py @@ -0,0 +1,49 @@ +from django.shortcuts import get_object_or_404, render +from django.views.generic.edit import UpdateView, CreateView + +from .models import Entry, Formats +# Create your views here. +from .tables import EntryTable +from .filters import EntryFilter +from django_filters.views import FilterView + +from django_tables2 import SingleTableView, SingleTableMixin + +class EntryUpdateView(UpdateView): + model = Entry + template_name = "oef/entry_update_view.html" + fields = ['modality', 'exam_code', 'exam_name', 'questions', 'comments'] + +class FormatsCreateView(CreateView): + model = Formats + template_name_suffix = "_update_form" + fields = ['name', 'format', 'inherits'] + +class FormatsUpdateView(UpdateView): + model = Formats + template_name_suffix = "_update_form" + fields = ['name', 'format', 'inherits'] + +class EntryTableView(SingleTableMixin, FilterView): + model = Entry + table_class = EntryTable + + template_name = "oef/entry_table.html" + + filterset_class = EntryFilter + +def formats_view(request): + formats = Formats.objects.all() + return render(request, "oef/formats_view.html", {"formats": formats}) + +def formats_apply(request, pk): + format = get_object_or_404(Formats, pk=pk) + + search_text = format.get_format() + + entries = Entry.objects.filter(questions__icontains=search_text) + + entry_list = [] + for entry in entries: + entry_list.append((entry, entry.questions.replace(search_text, ""))) + return render(request, "oef/formats_apply.html", {"format": format, "entry_list": entry_list, "entries": entries}) diff --git a/rad/settings.py b/rad/settings.py index d4239ed9..805c5c46 100644 --- a/rad/settings.py +++ b/rad/settings.py @@ -51,6 +51,7 @@ INSTALLED_APPS = [ "atlas", "rcr", "rota", + "oef", "reversion", "debug_toolbar", "django_tables2", diff --git a/rad/urls.py b/rad/urls.py index b9f7b773..4a933127 100644 --- a/rad/urls.py +++ b/rad/urls.py @@ -66,6 +66,7 @@ urlpatterns = [ path("atlas/", include("atlas.urls"), name="atlas"), path("rcr/", include("rcr.urls"), name="rcr"), path("rota/", include("rota.urls"), name="rota"), + path("oef/", include("oef.urls"), name="oef"), path( "accounts/check_users", views.accounts_check_users, name="accounts_check_users" ),