Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion django/researchdata/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class PromptAdminView(GenericAdminView):
@admin.register(models.Response)
class ResponseAdminView(GenericAdminView):
"""
Customise the content of the list of Prompts in the Django admin
Customise the content of the list of Responses in the Django admin
"""
list_display = ('id',
'response_content',
Expand All @@ -118,3 +118,22 @@ class ResponseAdminView(GenericAdminView):
'meta_lastupdated_datetime')
list_filter = ('admin_approved',)
actions = (approve, unapprove)


@admin.register(models.NotRelevantReport)
class NotRelevantReportAdminView(GenericAdminView):
"""
Customise the content of the list of NotRelevantReports in the Django admin
"""
list_display = ('id',
'prompt',
'user_search_query',
'meta_created_datetime')


@admin.register(models.DataInsert)
class DataInsertAdminView(GenericAdminView):
"""
Customise the content of the list of DataInserts in the Django admin
"""
list_display = ('id', 'meta_created_datetime')
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Generated by Django 4.2.17 on 2024-12-24 21:53

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('researchdata', '0001_initial'),
]

operations = [
migrations.CreateModel(
name='DataInsert',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('create_triggers', models.TextField(blank=True, help_text='Include a comma separated list of new Triggers to create them, e.g. "apple, banana, pear"', null=True)),
('meta_created_datetime', models.DateTimeField(auto_now_add=True, verbose_name='created')),
],
),
migrations.CreateModel(
name='NotRelevantReport',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('user_search_query', models.TextField()),
('admin_notes', models.TextField(blank=True, null=True)),
('meta_created_datetime', models.DateTimeField(auto_now_add=True, verbose_name='created')),
('prompt', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='notrelevantreports', to='researchdata.prompt')),
],
options={
'ordering': ['-meta_created_datetime'],
},
),
]
44 changes: 44 additions & 0 deletions django/researchdata/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,47 @@ def __str__(self):

class Meta:
ordering = ['-meta_created_datetime']


class NotRelevantReport(models.Model):
"""
The report that a user submits if information is not relevant to their search
"""

prompt = models.ForeignKey(Prompt, related_name='notrelevantreports', on_delete=models.PROTECT)
user_search_query = models.TextField()

admin_notes = models.TextField(blank=True, null=True)

meta_created_datetime = models.DateTimeField(auto_now_add=True, verbose_name="created")

def __str__(self):
return f'Response #{self.id} - {str(self.meta_created_datetime)[:19]}: {textwrap.shorten(self.response_content, width=50, placeholder="...")}'

class Meta:
ordering = ['-meta_created_datetime']


class DataInsert(models.Model):
"""
A model that allows data to be easily inserted into other models in this project
"""

create_triggers = models.TextField(
blank=True, null=True,
help_text='Include a comma separated list of new Triggers to create them, e.g. "apple, banana, pear"'
)
meta_created_datetime = models.DateTimeField(auto_now_add=True, verbose_name="created")

def save(self, *args, **kwargs):
"""
Create objects for each specified field.
"""
# Triggers
if self.create_triggers:
for trigger in self.create_triggers.split(','):
t = trigger.strip()
if len(t):
Trigger.objects.get_or_create(trigger_text=t)
# Save this DataInsert object
super().save(*args, **kwargs)
1 change: 1 addition & 0 deletions django/researchdata/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@
urlpatterns = [
path('prompt/get/', views.prompt_get, name='prompt-get'),
path('response/post/', views.response_post, name='response-post'),
path('notrelevantreport/post/', views.notrelevantreport_post, name='notrelevantreport-post'),
]
75 changes: 59 additions & 16 deletions django/researchdata/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.http import JsonResponse
from django.forms.models import model_to_dict
from django.views.decorators.csrf import csrf_exempt
from . import models

Expand All @@ -9,21 +10,48 @@ def prompt_get(request):
"""

user_search_query = request.GET.get('user_search_query', '')
search_exact = int(request.GET.get('search_exact', '0'))
topics_exclude_get = request.GET.get('topics_exclude', '')
topics_exclude = []
if len(topics_exclude_get):
for topic in topics_exclude_get.split(','):
if topic:
topics_exclude.append(int(topic))
if len(user_search_query):
# Try to find a match for each word in user search query
# E.g. if user searches "the ukraine war" then match "ukraine" and return prompt data
# If multiple found, return the highest priority prompt
for word in user_search_query.split(' '):
prompt = models.Prompt.objects.filter(triggers__trigger_text__icontains=word).order_by('-priority').first()
# If user has requested an exact search, include the full search term
# Otherwise, do a search for each individual word in the search query
search_terms = [user_search_query] if search_exact == 1 else user_search_query.split(' ')
for search_term in search_terms:
# Clean search term, if user hasn't asked for an exact search
# e.g. if user searches "walking" or "walks" remove the extra chars to just get "walk"
if search_exact == 0:
# Remove 's' from end, as this is likely plural
if len(search_term) > 2 and search_term.endswith('s'):
search_term = search_term[:-1]
# Remove 'ing' and 'ies' from end
elif len(search_term) > 4 and search_term.endswith('ing') or search_term.endswith('ies'):
search_term = search_term[:-3]
# Find a prompt that matches the search term
prompts = models.Prompt.objects.all()
if len(topics_exclude):
prompts = prompts.exclude(id__in=topics_exclude)
prompt = prompts.filter(triggers__trigger_text__icontains=search_term).order_by('-priority').first()
# Build the response data dict, with a list of available topics
response_data = {
'topics': [
{**model_to_dict(topic), **{'excluded': 1 if topic.id in topics_exclude else 0}}
for topic in models.TopicGroup.objects.all()
]
}
# If a prompt is found, add it to the response dict
if prompt:
return JsonResponse({
'prompt': {
'id': prompt.id,
'topic': str(prompt.topic),
'prompt_content': prompt.prompt_content.replace('\n', '<br>'),
'response_required': prompt.response_required
}
})
response_data['prompt'] = {
'id': prompt.id,
'topic': str(prompt.topic),
'prompt_content': prompt.prompt_content.replace('\n', '<br>'),
'response_required': prompt.response_required
}
return JsonResponse(response_data)

# If a matching prompt can't be found then return a False prompt to client
return JsonResponse({'prompt': False})
Expand All @@ -37,14 +65,29 @@ def response_post(request):

user_response_content = request.POST.get('user_response_content', '')
active_prompt_id = request.POST.get('active_prompt_id', '')

response = None

if len(user_response_content) and len(active_prompt_id):
response = models.Response.objects.create(
prompt=models.Prompt.objects.get(id=active_prompt_id),
response_content=user_response_content
)

data = {'response_saved': 1 if response else 0}
return JsonResponse(data)


@csrf_exempt
def notrelevantreport_post(request):
"""
Function-based view to create a new NotRelevantReport data object
"""

active_prompt_id = request.POST.get('active_prompt_id', '')
user_search_query = request.POST.get('user_search_query', '')
report = None
if len(user_search_query) and len(active_prompt_id):
report = models.NotRelevantReport.objects.create(
prompt=models.Prompt.objects.get(id=active_prompt_id),
user_search_query=user_search_query
)
data = {'report_saved': 1 if report else 0}
return JsonResponse(data)
3 changes: 2 additions & 1 deletion web_extension_chrome/local_settings.example.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ Don't change the const names as these are used elsewhere in the extension.
// Provide the URLs for the API request (used in popup.js to get/post data from/to the API)
const apiUrl = 'http://localhost:8001';
const apiUrlPromptGet = `${apiUrl}/data/prompt/get/`;
const apiUrlPromptPost = `${apiUrl}/data/response/post/`;
const apiUrlResponsePost = `${apiUrl}/data/response/post/`;
const apiUrlNotRelevantReportPost = `${apiUrl}/data/notrelevantreport/post/`;
2 changes: 1 addition & 1 deletion web_extension_chrome/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Ethical Interface",
"version": "1.0.0",
"version": "1.1.0",
"description": "Engage with the Ethical Interface research project run by Rosie Graham at the University of Birmingham",
"permissions": ["tabs"],
"host_permissions": ["<all_urls>"],
Expand Down
20 changes: 20 additions & 0 deletions web_extension_chrome/popup.css
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,26 @@ strong {
text-align: center;
}

#notrelevanttoggle {
width: fit-content;
margin: 3em auto 1em auto;
color: #AAA;
text-decoration: underline;
cursor: pointer;
}

#notrelevant {
display: none;
background: rgba(255, 255, 255, 0.1);
padding: 1em;
margin: 1em 0;
}

#notrelevant span {
text-decoration: underline;
cursor: pointer;
}

#popup-footer {
border-top: 0.2em solid white;
padding-top: 0.5em;
Expand Down
Loading
Loading