Skip to content

Commit

Permalink
Merge remote-tracking branch 'ietf-tools/main' into feat/status-message
Browse files Browse the repository at this point in the history
  • Loading branch information
rjsparks committed Aug 7, 2024
2 parents dfa3732 + f921cdb commit b21661c
Show file tree
Hide file tree
Showing 36 changed files with 463 additions and 270 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ Many developers are using [VS Code](https://code.visualstudio.com/) and taking a
If VS Code is not available to you, in your clone, type `cd docker; ./run`
Once the containers are started, run the tests to make sure your checkout is a good place to start from (all tests should pass - if any fail, ask for help at tools-develop@). Inside the app container's shell type:
Once the containers are started, run the tests to make sure your checkout is a good place to start from (all tests should pass - if any fail, ask for help at tools-help@). Inside the app container's shell type:
```sh
ietf/manage.py test --settings=settings_test
```
Expand Down
2 changes: 1 addition & 1 deletion client/agenda/Agenda.vue
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ const meetingUpdated = computed(() => {
if (!agendaStore.meeting.updated) { return false }
const updatedDatetime = DateTime.fromISO(agendaStore.meeting.updated).setZone(agendaStore.timezone)
if (!updatedDatetime.isValid || updatedDatetime < DateTime.fromISO('1980-01-01')) {
if (!updatedDatetime.isValid) {
return false
}
Expand Down
Empty file added ietf/admin/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions ietf/admin/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright The IETF Trust 2024, All Rights Reserved
from django.contrib.admin import apps as admin_apps


class AdminConfig(admin_apps.AdminConfig):
default_site = "ietf.admin.sites.AdminSite"
15 changes: 15 additions & 0 deletions ietf/admin/sites.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright The IETF Trust 2024, All Rights Reserved
from django.contrib.admin import AdminSite as _AdminSite
from django.conf import settings
from django.utils.safestring import mark_safe


class AdminSite(_AdminSite):
site_title = "Datatracker admin"

@staticmethod
def site_header():
if settings.SERVER_MODE == "production":
return "Datatracker administration"
else:
return mark_safe('Datatracker administration <span class="text-danger">&delta;</span>')
33 changes: 16 additions & 17 deletions ietf/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@

from urllib.parse import urlencode

from django.conf import settings
from django.apps import apps as django_apps
from django.core.exceptions import ObjectDoesNotExist
from django.utils.module_loading import autodiscover_modules


import debug # pyflakes:ignore

Expand All @@ -35,28 +37,25 @@
_module_dict[_name] = _api
_api_list.append((_name, _api))

def populate_api_list():
for app_config in django_apps.get_app_configs():
_module_dict = globals()
if '.' in app_config.name:
_root, _name = app_config.name.split('.', 1)
if _root == 'ietf':
if not '.' in _name:
_api = Api(api_name=_name)
_module_dict[_name] = _api
_api_list.append((_name, _api))

def autodiscover():
"""
Auto-discover INSTALLED_APPS resources.py modules and fail silently when
not present. This forces an import on them to register any admin bits they
not present. This forces an import on them to register any resources they
may want.
"""
autodiscover_modules("resources")

from importlib import import_module
from django.conf import settings
from django.utils.module_loading import module_has_submodule

for app in settings.INSTALLED_APPS:
mod = import_module(app)
# Attempt to import the app's admin module.
try:
import_module('%s.resources' % (app, ))
except:
# Decide whether to bubble up this error. If the app just
# doesn't have an admin module, we can ignore the error
# attempting to import it, otherwise we want it to bubble up.
if module_has_submodule(mod, "resources"):
raise

class ModelResource(tastypie.resources.ModelResource):
def generate_cache_key(self, *args, **kwargs):
Expand Down
1 change: 1 addition & 0 deletions ietf/api/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ class Serializer(): ...
class ToOneField(tastypie.fields.ToOneField): ...
class TimedeltaField(tastypie.fields.ApiField): ...

def populate_api_list() -> None: ...
def autodiscover() -> None: ...
15 changes: 15 additions & 0 deletions ietf/api/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from django.apps import AppConfig
from . import populate_api_list


class ApiConfig(AppConfig):
name = "ietf.api"

def ready(self):
"""Hook to do init after the app registry is fully populated
Importing models or accessing the app registry is ok here, but do not
interact with the database. See
https://docs.djangoproject.com/en/4.2/ref/applications/#django.apps.AppConfig.ready
"""
populate_api_list()
1 change: 1 addition & 0 deletions ietf/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from ietf.submit import views as submit_views
from ietf.utils.urls import url


api.autodiscover()

urlpatterns = [
Expand Down
2 changes: 2 additions & 0 deletions ietf/doc/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ def generate_draft_bibxml_files_task(days=7, process_all=False):
If process_all is False (the default), processes only docs with new revisions
in the last specified number of days.
"""
if not process_all and days < 1:
raise ValueError("Must call with days >= 1 or process_all=True")
ensure_draft_bibxml_path_exists()
doc_events = NewRevisionDocEvent.objects.filter(
type="new_revision",
Expand Down
7 changes: 7 additions & 0 deletions ietf/doc/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3013,6 +3013,13 @@ def test_pdfized(self):
with (Path(dir) / f'{draft.name}-{r:02d}.txt').open('w') as f:
f.write('text content')

self.assertTrue(
login_testing_unauthorized(
self,
PersonFactory().user.username,
urlreverse(self.view, kwargs={"name": draft.name}),
)
)
self.should_succeed(dict(name=rfc.name))
self.should_succeed(dict(name=draft.name))
for r in range(0,2):
Expand Down
119 changes: 66 additions & 53 deletions ietf/doc/tests_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
)

class TaskTests(TestCase):
settings_temp_path_overrides = TestCase.settings_temp_path_overrides + ["DERIVED_DIR"]

@mock.patch("ietf.doc.tasks.in_draft_expire_freeze")
@mock.patch("ietf.doc.tasks.get_expired_drafts")
@mock.patch("ietf.doc.tasks.expirable_drafts")
Expand Down Expand Up @@ -63,8 +61,8 @@ def test_expire_ids_task(

# test that an exception is raised
in_draft_expire_freeze_mock.side_effect = RuntimeError
with self.assertRaises(RuntimeError): (
expire_ids_task())
with self.assertRaises(RuntimeError):
expire_ids_task()

@mock.patch("ietf.doc.tasks.send_expire_warning_for_draft")
@mock.patch("ietf.doc.tasks.get_soon_to_expire_drafts")
Expand Down Expand Up @@ -98,37 +96,42 @@ def test_expire_last_calls_task(self, mock_get_expired, mock_expire):
self.assertEqual(mock_expire.call_args_list[1], mock.call(docs[1]))
self.assertEqual(mock_expire.call_args_list[2], mock.call(docs[2]))

@mock.patch("ietf.doc.tasks.generate_idnits2_rfc_status")
def test_generate_idnits2_rfc_status_task(self, mock_generate):

class Idnits2SupportTests(TestCase):
settings_temp_path_overrides = TestCase.settings_temp_path_overrides + ['DERIVED_DIR']

@mock.patch("ietf.doc.tasks.generate_idnits2_rfcs_obsoleted")
def test_generate_idnits2_rfcs_obsoleted_task(self, mock_generate):
mock_generate.return_value = "dåtå"
generate_idnits2_rfc_status_task()
generate_idnits2_rfcs_obsoleted_task()
self.assertEqual(mock_generate.call_count, 1)
self.assertEqual(
"dåtå".encode("utf8"),
(Path(settings.DERIVED_DIR) / "idnits2-rfc-status").read_bytes(),
(Path(settings.DERIVED_DIR) / "idnits2-rfcs-obsoleted").read_bytes(),
)
@mock.patch("ietf.doc.tasks.generate_idnits2_rfcs_obsoleted")
def test_generate_idnits2_rfcs_obsoleted_task(self, mock_generate):

@mock.patch("ietf.doc.tasks.generate_idnits2_rfc_status")
def test_generate_idnits2_rfc_status_task(self, mock_generate):
mock_generate.return_value = "dåtå"
generate_idnits2_rfcs_obsoleted_task()
generate_idnits2_rfc_status_task()
self.assertEqual(mock_generate.call_count, 1)
self.assertEqual(
"dåtå".encode("utf8"),
(Path(settings.DERIVED_DIR) / "idnits2-rfcs-obsoleted").read_bytes(),
(Path(settings.DERIVED_DIR) / "idnits2-rfc-status").read_bytes(),
)

@mock.patch("ietf.doc.tasks.ensure_draft_bibxml_path_exists")
@mock.patch("ietf.doc.tasks.update_or_create_draft_bibxml_file")
def test_generate_draft_bibxml_files_task(self, mock_create, mock_ensure_path):

class BIBXMLSupportTests(TestCase):
def setUp(self):
super().setUp()
now = timezone.now()
very_old_event = NewRevisionDocEventFactory(
self.very_old_event = NewRevisionDocEventFactory(
time=now - datetime.timedelta(days=1000), rev="17"
)
old_event = NewRevisionDocEventFactory(
self.old_event = NewRevisionDocEventFactory(
time=now - datetime.timedelta(days=8), rev="03"
)
young_event = NewRevisionDocEventFactory(
self.young_event = NewRevisionDocEventFactory(
time=now - datetime.timedelta(days=6), rev="06"
)
# a couple that should always be ignored
Expand All @@ -141,62 +144,72 @@ def test_generate_draft_bibxml_files_task(self, mock_create, mock_ensure_path):
rev="09",
doc__type_id="rfc",
)

# Get rid of the "00" events created by the factories -- they're just noise for this test
NewRevisionDocEvent.objects.filter(rev="00").delete()

# default args - look back 7 days
generate_draft_bibxml_files_task()
self.assertTrue(mock_ensure_path.called)
self.assertCountEqual(
mock_create.call_args_list, [mock.call(young_event.doc, young_event.rev)]
)
mock_create.reset_mock()
mock_ensure_path.reset_mock()

# shorter lookback
generate_draft_bibxml_files_task(days=5)
self.assertTrue(mock_ensure_path.called)
self.assertCountEqual(mock_create.call_args_list, [])
mock_create.reset_mock()
mock_ensure_path.reset_mock()

# longer lookback
generate_draft_bibxml_files_task(days=9)

@mock.patch("ietf.doc.tasks.ensure_draft_bibxml_path_exists")
@mock.patch("ietf.doc.tasks.update_or_create_draft_bibxml_file")
def test_generate_bibxml_files_for_all_drafts_task(self, mock_create, mock_ensure_path):
generate_draft_bibxml_files_task(process_all=True)
self.assertTrue(mock_ensure_path.called)
self.assertCountEqual(
mock_create.call_args_list,
[
mock.call(young_event.doc, young_event.rev),
mock.call(old_event.doc, old_event.rev),
mock.call(self.young_event.doc, self.young_event.rev),
mock.call(self.old_event.doc, self.old_event.rev),
mock.call(self.very_old_event.doc, self.very_old_event.rev),
],
)
mock_create.reset_mock()
mock_ensure_path.reset_mock()

# everything

# everything should still be tried, even if there's an exception
mock_create.side_effect = RuntimeError
generate_draft_bibxml_files_task(process_all=True)
self.assertTrue(mock_ensure_path.called)
self.assertCountEqual(
mock_create.call_args_list,
[
mock.call(young_event.doc, young_event.rev),
mock.call(old_event.doc, old_event.rev),
mock.call(very_old_event.doc, very_old_event.rev),
mock.call(self.young_event.doc, self.young_event.rev),
mock.call(self.old_event.doc, self.old_event.rev),
mock.call(self.very_old_event.doc, self.very_old_event.rev),
],
)

@mock.patch("ietf.doc.tasks.ensure_draft_bibxml_path_exists")
@mock.patch("ietf.doc.tasks.update_or_create_draft_bibxml_file")
def test_generate_bibxml_files_for_recent_drafts_task(self, mock_create, mock_ensure_path):
# default args - look back 7 days
generate_draft_bibxml_files_task()
self.assertTrue(mock_ensure_path.called)
self.assertCountEqual(
mock_create.call_args_list, [mock.call(self.young_event.doc, self.young_event.rev)]
)
mock_create.reset_mock()
mock_ensure_path.reset_mock()

# everything should still be tried, even if there's an exception
mock_create.side_effect = RuntimeError
generate_draft_bibxml_files_task(process_all=True)

# shorter lookback
generate_draft_bibxml_files_task(days=5)
self.assertTrue(mock_ensure_path.called)
self.assertCountEqual(mock_create.call_args_list, [])
mock_create.reset_mock()
mock_ensure_path.reset_mock()

# longer lookback
generate_draft_bibxml_files_task(days=9)
self.assertTrue(mock_ensure_path.called)
self.assertCountEqual(
mock_create.call_args_list,
[
mock.call(young_event.doc, young_event.rev),
mock.call(old_event.doc, old_event.rev),
mock.call(very_old_event.doc, very_old_event.rev),
mock.call(self.young_event.doc, self.young_event.rev),
mock.call(self.old_event.doc, self.old_event.rev),
],
)

@mock.patch("ietf.doc.tasks.ensure_draft_bibxml_path_exists")
@mock.patch("ietf.doc.tasks.update_or_create_draft_bibxml_file")
def test_generate_bibxml_files_for_recent_drafts_task_with_bad_value(self, mock_create, mock_ensure_path):
with self.assertRaises(ValueError):
generate_draft_bibxml_files_task(days=0)
self.assertFalse(mock_create.called)
self.assertFalse(mock_ensure_path.called)
3 changes: 2 additions & 1 deletion ietf/doc/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright The IETF Trust 2011-2020, All Rights Reserved
# Copyright The IETF Trust 2011-2024, All Rights Reserved
# -*- coding: utf-8 -*-


Expand Down Expand Up @@ -1228,6 +1228,7 @@ def fuzzy_find_documents(name, rev=None):
FoundDocuments = namedtuple('FoundDocuments', 'documents matched_name matched_rev')
return FoundDocuments(docs, name, rev)


def bibxml_for_draft(doc, rev=None):

if rev is not None and rev != doc.rev:
Expand Down
14 changes: 7 additions & 7 deletions ietf/doc/views_doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@ def document_main(request, name, rev=None, document_html=False):
can_change_stream = bool(can_edit or roles)

file_urls, found_types = build_file_urls(doc)
if not request.user.is_authenticated:
file_urls = [fu for fu in file_urls if fu[0] != "pdfized"]
content = doc.text_or_error() # pyflakes:ignore
content = markup_txt.markup(maybe_split(content, split=split_content))

Expand Down Expand Up @@ -406,6 +408,8 @@ def document_main(request, name, rev=None, document_html=False):
latest_revision = None

file_urls, found_types = build_file_urls(doc)
if not request.user.is_authenticated:
file_urls = [fu for fu in file_urls if fu[0] != "pdfized"]
content = doc.text_or_error() # pyflakes:ignore
content = markup_txt.markup(maybe_split(content, split=split_content))

Expand Down Expand Up @@ -603,12 +607,7 @@ def document_main(request, name, rev=None, document_html=False):
additional_urls = doc.documenturl_set.exclude(tag_id='auth48')

# Stream description and name passing test
if doc.stream != None:
stream_desc = doc.stream.desc
stream = "draft-stream-" + doc.stream.slug
else:
stream_desc = "(None)"
stream = "(None)"
stream = ("draft-stream-" + doc.stream.slug) if doc.stream != None else "(None)"

html = None
js = None
Expand Down Expand Up @@ -647,7 +646,6 @@ def document_main(request, name, rev=None, document_html=False):
revisions=simple_diff_revisions if document_html else revisions,
snapshot=snapshot,
stream=stream,
stream_desc=stream_desc,
latest_revision=latest_revision,
latest_rev=latest_rev,
can_edit=can_edit,
Expand Down Expand Up @@ -1039,6 +1037,8 @@ def document_html(request, name, rev=None):
document_html=True,
)


@login_required
def document_pdfized(request, name, rev=None, ext=None):

found = fuzzy_find_documents(name, rev)
Expand Down
Loading

0 comments on commit b21661c

Please sign in to comment.