From 8362b45c8e94d5bf3585f27b2e821388d9b9fb14 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Tue, 17 Sep 2024 13:05:56 -0500 Subject: [PATCH 01/10] fix: optimize and debug has_role and can_manage_some_groups (#7949) * fix: optimize can_manage_some_groups * fix: improve cache key * refactor: extra_role_qs to kwargs and bugfix to cache key * fix: restrict groupman_role matches to active states * chore: styling, decommenting, black --- ietf/group/utils.py | 21 ++++-- ietf/ietfauth/utils.py | 162 +++++++++++++++++++++++++++++------------ 2 files changed, 131 insertions(+), 52 deletions(-) diff --git a/ietf/group/utils.py b/ietf/group/utils.py index 68b618120b..dcf9d83e6f 100644 --- a/ietf/group/utils.py +++ b/ietf/group/utils.py @@ -2,6 +2,7 @@ # -*- coding: utf-8 -*- import datetime +from itertools import chain from pathlib import Path from django.db.models import Q @@ -153,17 +154,23 @@ def can_manage_materials(user, group): def can_manage_session_materials(user, group, session): return has_role(user, 'Secretariat') or (group.has_role(user, group.features.matman_roles) and not session.is_material_submission_cutoff()) -# Maybe this should be cached... def can_manage_some_groups(user): if not user.is_authenticated: return False + authroles = set( + chain.from_iterable( + GroupFeatures.objects.values_list("groupman_authroles", flat=True) + ) + ) + extra_role_qs = dict() for gf in GroupFeatures.objects.all(): - for authrole in gf.groupman_authroles: - if has_role(user, authrole): - return True - if Role.objects.filter(name__in=gf.groupman_roles, group__type_id=gf.type_id, person__user=user).exists(): - return True - return False + extra_role_qs[f"{gf.type_id} groupman roles"] = Q( + name__in=gf.groupman_roles, + group__type_id=gf.type_id, + group__state__in=["active", "bof", "proposed"], + ) + return has_role(user, authroles, extra_role_qs=extra_role_qs) + def can_provide_status_update(user, group): if not group.features.acts_like_wg: diff --git a/ietf/ietfauth/utils.py b/ietf/ietfauth/utils.py index 6fa9cddbcb..d8bd67a4e0 100644 --- a/ietf/ietfauth/utils.py +++ b/ietf/ietfauth/utils.py @@ -38,9 +38,10 @@ def has_role(user, role_names, *args, **kwargs): """Determines whether user has any of the given standard roles given. Role names must be a list or, in case of a single value, a string.""" - if not isinstance(role_names, (list, tuple)): - role_names = [ role_names ] - + extra_role_qs = kwargs.get("extra_role_qs", None) + if not isinstance(role_names, (list, tuple, set)): + role_names = [role_names] + if not user or not user.is_authenticated: return False @@ -48,7 +49,13 @@ def has_role(user, role_names, *args, **kwargs): if not hasattr(user, "roles_check_cache"): user.roles_check_cache = {} - key = frozenset(role_names) + keynames = set(role_names) + if extra_role_qs: + keynames.update(set(extra_role_qs.keys())) + year = kwargs.get("year", None) + if year is not None: + keynames.add(f"nomcomyear{year}") + key = frozenset(keynames) if key not in user.roles_check_cache: try: person = user.person @@ -56,54 +63,119 @@ def has_role(user, role_names, *args, **kwargs): return False role_qs = { - "Area Director": Q(person=person, name__in=("pre-ad", "ad"), group__type="area", group__state="active"), - "Secretariat": Q(person=person, name="secr", group__acronym="secretariat"), - "IAB" : Q(person=person, name="member", group__acronym="iab"), - "IANA": Q(person=person, name="auth", group__acronym="iana"), - "RFC Editor": Q(person=person, name="auth", group__acronym="rpc"), - "ISE" : Q(person=person, name="chair", group__acronym="ise"), - "IAD": Q(person=person, name="admdir", group__acronym="ietf"), - "IETF Chair": Q(person=person, name="chair", group__acronym="ietf"), - "IETF Trust Chair": Q(person=person, name="chair", group__acronym="ietf-trust"), - "IRTF Chair": Q(person=person, name="chair", group__acronym="irtf"), - "RSAB Chair": Q(person=person, name="chair", group__acronym="rsab"), - "IAB Chair": Q(person=person, name="chair", group__acronym="iab"), - "IAB Executive Director": Q(person=person, name="execdir", group__acronym="iab"), - "IAB Group Chair": Q(person=person, name="chair", group__type="iab", group__state="active"), - "IAOC Chair": Q(person=person, name="chair", group__acronym="iaoc"), - "WG Chair": Q(person=person,name="chair", group__type="wg", group__state__in=["active","bof", "proposed"]), - "WG Secretary": Q(person=person,name="secr", group__type="wg", group__state__in=["active","bof", "proposed"]), - "RG Chair": Q(person=person,name="chair", group__type="rg", group__state__in=["active","proposed"]), - "RG Secretary": Q(person=person,name="secr", group__type="rg", group__state__in=["active","proposed"]), - "AG Secretary": Q(person=person,name="secr", group__type="ag", group__state__in=["active"]), - "RAG Secretary": Q(person=person,name="secr", group__type="rag", group__state__in=["active"]), - "Team Chair": Q(person=person,name="chair", group__type="team", group__state="active"), - "Program Lead": Q(person=person,name="lead", group__type="program", group__state="active"), - "Program Secretary": Q(person=person,name="secr", group__type="program", group__state="active"), - "Program Chair": Q(person=person,name="chair", group__type="program", group__state="active"), - "EDWG Chair": Q(person=person, name="chair", group__type="edwg", group__state="active"), - "Nomcom Chair": Q(person=person, name="chair", group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')), - "Nomcom Advisor": Q(person=person, name="advisor", group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')), - "Nomcom": Q(person=person, group__type="nomcom", group__acronym__icontains=kwargs.get('year', '0000')), - "Liaison Manager": Q(person=person,name="liaiman",group__type="sdo",group__state="active", ), - "Authorized Individual": Q(person=person,name="auth",group__type="sdo",group__state="active", ), - "Recording Manager": Q(person=person,name="recman",group__type="ietf",group__state="active", ), - "Reviewer": Q(person=person, name="reviewer", group__state="active"), - "Review Team Secretary": Q(person=person, name="secr", group__reviewteamsettings__isnull=False,group__state="active", ), - "IRSG Member": (Q(person=person, name="member", group__acronym="irsg") | Q(person=person, name="chair", group__acronym="irtf") | Q(person=person, name="atlarge", group__acronym="irsg")), - "RSAB Member": Q(person=person, name="member", group__acronym="rsab"), - "Robot": Q(person=person, name="robot", group__acronym="secretariat"), - } - - filter_expr = Q(pk__in=[]) # ensure empty set is returned if no other terms are added + "Area Director": Q( + name__in=("pre-ad", "ad"), group__type="area", group__state="active" + ), + "Secretariat": Q(name="secr", group__acronym="secretariat"), + "IAB": Q(name="member", group__acronym="iab"), + "IANA": Q(name="auth", group__acronym="iana"), + "RFC Editor": Q(name="auth", group__acronym="rpc"), + "ISE": Q(name="chair", group__acronym="ise"), + "IAD": Q(name="admdir", group__acronym="ietf"), + "IETF Chair": Q(name="chair", group__acronym="ietf"), + "IETF Trust Chair": Q(name="chair", group__acronym="ietf-trust"), + "IRTF Chair": Q(name="chair", group__acronym="irtf"), + "RSAB Chair": Q(name="chair", group__acronym="rsab"), + "IAB Chair": Q(name="chair", group__acronym="iab"), + "IAB Executive Director": Q(name="execdir", group__acronym="iab"), + "IAB Group Chair": Q( + name="chair", group__type="iab", group__state="active" + ), + "IAOC Chair": Q(name="chair", group__acronym="iaoc"), + "WG Chair": Q( + name="chair", + group__type="wg", + group__state__in=["active", "bof", "proposed"], + ), + "WG Secretary": Q( + name="secr", + group__type="wg", + group__state__in=["active", "bof", "proposed"], + ), + "RG Chair": Q( + name="chair", group__type="rg", group__state__in=["active", "proposed"] + ), + "RG Secretary": Q( + name="secr", group__type="rg", group__state__in=["active", "proposed"] + ), + "AG Secretary": Q( + name="secr", group__type="ag", group__state__in=["active"] + ), + "RAG Secretary": Q( + name="secr", group__type="rag", group__state__in=["active"] + ), + "Team Chair": Q(name="chair", group__type="team", group__state="active"), + "Program Lead": Q( + name="lead", group__type="program", group__state="active" + ), + "Program Secretary": Q( + name="secr", group__type="program", group__state="active" + ), + "Program Chair": Q( + name="chair", group__type="program", group__state="active" + ), + "EDWG Chair": Q(name="chair", group__type="edwg", group__state="active"), + "Nomcom Chair": Q( + name="chair", + group__type="nomcom", + group__acronym__icontains=kwargs.get("year", "0000"), + ), + "Nomcom Advisor": Q( + name="advisor", + group__type="nomcom", + group__acronym__icontains=kwargs.get("year", "0000"), + ), + "Nomcom": Q( + group__type="nomcom", + group__acronym__icontains=kwargs.get("year", "0000"), + ), + "Liaison Manager": Q( + name="liaiman", + group__type="sdo", + group__state="active", + ), + "Authorized Individual": Q( + name="auth", + group__type="sdo", + group__state="active", + ), + "Recording Manager": Q( + name="recman", + group__type="ietf", + group__state="active", + ), + "Reviewer": Q(name="reviewer", group__state="active"), + "Review Team Secretary": Q( + name="secr", + group__reviewteamsettings__isnull=False, + group__state="active", + ), + "IRSG Member": ( + Q(name="member", group__acronym="irsg") + | Q(name="chair", group__acronym="irtf") + | Q(name="atlarge", group__acronym="irsg") + ), + "RSAB Member": Q(name="member", group__acronym="rsab"), + "Robot": Q(name="robot", group__acronym="secretariat"), + } + + filter_expr = Q( + pk__in=[] + ) # ensure empty set is returned if no other terms are added for r in role_names: filter_expr |= role_qs[r] + if extra_role_qs: + for r in extra_role_qs: + filter_expr |= extra_role_qs[r] - user.roles_check_cache[key] = bool(Role.objects.filter(filter_expr).exists()) + user.roles_check_cache[key] = bool( + Role.objects.filter(person=person).filter(filter_expr).exists() + ) return user.roles_check_cache[key] + # convenient decorator def passes_test_decorator(test_func, message): From 97b719505ee162d0c55d57edbdd029f766c27a71 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Tue, 17 Sep 2024 13:06:10 -0500 Subject: [PATCH 02/10] test: focus on a helper rather than a whole view (#7951) --- ietf/meeting/tests_views.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py index e4f62838de..f951eb682d 100644 --- a/ietf/meeting/tests_views.py +++ b/ietf/meeting/tests_views.py @@ -39,7 +39,7 @@ from ietf.group.models import Group, Role, GroupFeatures from ietf.group.utils import can_manage_group from ietf.person.models import Person, PersonalApiKey -from ietf.meeting.helpers import can_approve_interim_request, can_view_interim_request, preprocess_assignments_for_agenda +from ietf.meeting.helpers import can_approve_interim_request, can_request_interim_meeting, can_view_interim_request, preprocess_assignments_for_agenda from ietf.meeting.helpers import send_interim_approval_request, AgendaKeywordTagger from ietf.meeting.helpers import send_interim_meeting_cancellation_notice, send_interim_session_cancellation_notice from ietf.meeting.helpers import send_interim_minutes_reminder, populate_important_dates, update_important_dates @@ -7334,10 +7334,7 @@ def test_cannot_request_interim(self): for gf in GroupFeatures.objects.filter(has_meetings=True): for role_name in all_role_names - set(gf.groupman_roles): role = RoleFactory(group__type_id=gf.type_id,name_id=role_name) - self.client.login(username=role.person.user.username, password=role.person.user.username+'+password') - r = self.client.get(url) - self.assertEqual(r.status_code, 403) - self.client.logout() + self.assertFalse(can_request_interim_meeting(role.person.user)) def test_appears_on_upcoming(self): url = urlreverse('ietf.meeting.views.upcoming') From c6389ba65fdefdce72661f3ee3d21f7d708da841 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Wed, 18 Sep 2024 16:02:00 -0300 Subject: [PATCH 03/10] fix: only send state change notifications once (#7953) * feat: split state_change_event create / save * refactor: avoid double-save in rfceditor.py * feat: only send notifications on event creation * test: update test_notification_signal_receiver() * chore: update comment --- ietf/community/models.py | 5 ++++- ietf/community/tests.py | 27 +++++++++++++++++---------- ietf/doc/utils.py | 25 ++++++++++++++++++++++--- ietf/sync/rfceditor.py | 13 +++++++------ 4 files changed, 50 insertions(+), 20 deletions(-) diff --git a/ietf/community/models.py b/ietf/community/models.py index b1295461d6..4d820eb0f1 100644 --- a/ietf/community/models.py +++ b/ietf/community/models.py @@ -104,12 +104,15 @@ def notify_events(sender, instance, **kwargs): if not isinstance(instance, DocEvent): return + if not kwargs.get("created", False): + return # only notify on creation + if instance.doc.type_id != 'draft': return if getattr(instance, "skip_community_list_notification", False): return - + # kludge alert: queuing a celery task in response to a signal can cause unexpected attempts to # start a Celery task during tests. To prevent this, don't queue a celery task if we're running # tests. diff --git a/ietf/community/tests.py b/ietf/community/tests.py index 84e4370771..181e9e0fa6 100644 --- a/ietf/community/tests.py +++ b/ietf/community/tests.py @@ -17,6 +17,7 @@ import ietf.community.views from ietf.group.models import Group from ietf.group.utils import setup_default_community_list_for_group +from ietf.doc.factories import DocumentFactory from ietf.doc.models import State from ietf.doc.utils import add_state_change_event from ietf.person.models import Person, Email, Alias @@ -439,39 +440,45 @@ def test_notification_signal_receiver(self, mock_notify_task): This implicitly tests that notify_events is hooked up to the post_save signal. """ # Arbitrary model that's not a DocEvent - p = PersonFactory() + person = PersonFactory() mock_notify_task.reset_mock() # clear any calls that resulted from the factories # be careful overriding SERVER_MODE - we do it here because the method # under test does not make this call when in "test" mode with override_settings(SERVER_MODE="not-test"): - p.save() + person.save() self.assertFalse(mock_notify_task.delay.called) - d = DocEventFactory() - mock_notify_task.reset_mock() # clear any calls that resulted from the factories + # build a DocEvent that is not yet persisted + doc = DocumentFactory() + d = DocEventFactory.build(by=person, doc=doc) + # mock_notify_task.reset_mock() # clear any calls that resulted from the factories # be careful overriding SERVER_MODE - we do it here because the method # under test does not make this call when in "test" mode with override_settings(SERVER_MODE="not-test"): d.save() - self.assertEqual(mock_notify_task.delay.call_count, 1) + self.assertEqual(mock_notify_task.delay.call_count, 1, "notify_task should be run on creation of DocEvent") self.assertEqual(mock_notify_task.delay.call_args, mock.call(event_id = d.pk)) mock_notify_task.reset_mock() + with override_settings(SERVER_MODE="not-test"): + d.save() + self.assertFalse(mock_notify_task.delay.called, "notify_task should not be run save of on existing DocEvent") + + mock_notify_task.reset_mock() + d = DocEventFactory.build(by=person, doc=doc) d.skip_community_list_notification = True # be careful overriding SERVER_MODE - we do it here because the method # under test does not make this call when in "test" mode with override_settings(SERVER_MODE="not-test"): d.save() - self.assertFalse(mock_notify_task.delay.called) + self.assertFalse(mock_notify_task.delay.called, "notify_task should not run when skip_community_list_notification is set") - del(d.skip_community_list_notification) - d.doc.type_id="rfc" # not "draft" - d.doc.save() + d = DocEventFactory.build(by=person, doc=DocumentFactory(type_id="rfc")) # be careful overriding SERVER_MODE - we do it here because the method # under test does not make this call when in "test" mode with override_settings(SERVER_MODE="not-test"): d.save() - self.assertFalse(mock_notify_task.delay.called) + self.assertFalse(mock_notify_task.delay.called, "notify_task should not run on a document with type 'rfc'") @mock.patch("ietf.utils.mail.send_mail_text") def test_notify_event_to_subscribers(self, mock_send_mail_text): diff --git a/ietf/doc/utils.py b/ietf/doc/utils.py index 74000e598b..97243a20d6 100644 --- a/ietf/doc/utils.py +++ b/ietf/doc/utils.py @@ -398,8 +398,12 @@ def get_unicode_document_content(key, filename, codec='utf-8', errors='ignore'): def tags_suffix(tags): return ("::" + "::".join(t.name for t in tags)) if tags else "" -def add_state_change_event(doc, by, prev_state, new_state, prev_tags=None, new_tags=None, timestamp=None): - """Add doc event to explain that state change just happened.""" + +def new_state_change_event(doc, by, prev_state, new_state, prev_tags=None, new_tags=None, timestamp=None): + """Create unsaved doc event to explain that state change just happened + + Returns None if no state change occurred. + """ if prev_state and new_state: assert prev_state.type_id == new_state.type_id @@ -419,7 +423,22 @@ def add_state_change_event(doc, by, prev_state, new_state, prev_tags=None, new_t e.desc += " from %s" % (prev_state.name + tags_suffix(prev_tags)) if timestamp: e.time = timestamp - e.save() + return e # not saved! + + +def add_state_change_event(doc, by, prev_state, new_state, prev_tags=None, new_tags=None, timestamp=None): + """Add doc event to explain that state change just happened. + + Returns None if no state change occurred. + + Note: Creating a state change DocEvent will trigger notifications to be sent to people subscribed + to the doc via a CommunityList on its first save(). If you need to adjust the event (say, changing + its desc) before that notification is sent, use new_state_change_event() instead and save the + event after making your changes. + """ + e = new_state_change_event(doc, by, prev_state, new_state, prev_tags, new_tags, timestamp) + if e is not None: + e.save() return e diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py index d3371ea36c..6b3482f10d 100644 --- a/ietf/sync/rfceditor.py +++ b/ietf/sync/rfceditor.py @@ -21,7 +21,7 @@ from ietf.doc.models import ( Document, State, StateType, DocEvent, DocRelationshipName, DocTagName, RelatedDocument, RelatedDocHistory ) from ietf.doc.expire import move_draft_files_to_archive -from ietf.doc.utils import add_state_change_event, prettify_std_name, update_action_holders +from ietf.doc.utils import add_state_change_event, new_state_change_event, prettify_std_name, update_action_holders from ietf.group.models import Group from ietf.ipr.models import IprDocRel from ietf.name.models import StdLevelName, StreamName @@ -202,11 +202,14 @@ def update_drafts_from_queue(drafts): if prev_state != next_state: d.set_state(next_state) - e = add_state_change_event(d, system, prev_state, next_state) + e = new_state_change_event(d, system, prev_state, next_state) # unsaved + if e: + if auth48: + e.desc = re.sub(r"(.*)", "\\1" % auth48, e.desc) + e.save() + events.append(e) if auth48: - e.desc = re.sub(r"(.*)", "\\1" % auth48, e.desc) - e.save() # Create or update the auth48 URL whether or not this is a state expected to have one. d.documenturl_set.update_or_create( tag_id='auth48', # look up existing based on this field @@ -215,8 +218,6 @@ def update_drafts_from_queue(drafts): else: # Remove any existing auth48 URL when an update does not have one. d.documenturl_set.filter(tag_id='auth48').delete() - if e: - events.append(e) changed.add(name) From 35074660dc18cb2ede7c209312dfb2ab32deb740 Mon Sep 17 00:00:00 2001 From: Lars Eggert Date: Wed, 18 Sep 2024 12:08:01 -0700 Subject: [PATCH 04/10] chore: Prevent test suite artifact creation in work directory (#6438) * chore: Prevent test suite artifact creation in work directory Also remove a few other stale test assets while I'm here. * Try and fix CI * Change IDSUBMIT_REPOSITORY_PATH * Make the dir * test: clean up media/nomcom directories * test: de-dup settings_temp_path_overrides * chore: remove debug * refactor: avoid premature import of test_utils * refactor: drop useless lambda wrapper --------- Co-authored-by: Jennifer Richards --- dev/tests/settings_local.py | 4 +- docker/configs/settings_local.py | 4 +- docker/scripts/app-create-dirs.sh | 8 +- ietf/meeting/tests_js.py | 11 +- ietf/person/factories.py | 7 +- ietf/settings_test.py | 24 +- ietf/utils/test_utils.py | 13 +- media/.gitignore | 1 - media/photo/nopictureavailable.jpg | Bin 1618 -> 0 bytes media/photo/profile-default.jpg | Bin 1397 -> 0 bytes test/data/profile-default.jpg | Bin 1397 -> 0 bytes test/data/youtube-discovery.json | 10879 ------------------------- test/data/youtube-playlistid.json | 1 - test/data/youtube-playlistitems.json | 1 - test/lib/.gitignore | 1 - test/lib/README | 9 - test/media/floor/.gitignore | 1 - test/media/photo/.gitignore | 1 - 18 files changed, 33 insertions(+), 10932 deletions(-) delete mode 100644 media/.gitignore delete mode 100644 media/photo/nopictureavailable.jpg delete mode 100644 media/photo/profile-default.jpg delete mode 100644 test/data/profile-default.jpg delete mode 100644 test/data/youtube-discovery.json delete mode 100644 test/data/youtube-playlistid.json delete mode 100644 test/data/youtube-playlistitems.json delete mode 100644 test/lib/.gitignore delete mode 100644 test/lib/README delete mode 100644 test/media/floor/.gitignore delete mode 100644 test/media/photo/.gitignore diff --git a/dev/tests/settings_local.py b/dev/tests/settings_local.py index 8b5d90b1ec..20941359d4 100644 --- a/dev/tests/settings_local.py +++ b/dev/tests/settings_local.py @@ -17,8 +17,8 @@ } IDSUBMIT_IDNITS_BINARY = "/usr/local/bin/idnits" -IDSUBMIT_REPOSITORY_PATH = "test/id/" -IDSUBMIT_STAGING_PATH = "test/staging/" +IDSUBMIT_REPOSITORY_PATH = "/assets/ietfdata/doc/draft/repository" +IDSUBMIT_STAGING_PATH = "/assets/www6s/staging/" AGENDA_PATH = '/assets/www6s/proceedings/' MEETINGHOST_LOGO_PATH = AGENDA_PATH diff --git a/docker/configs/settings_local.py b/docker/configs/settings_local.py index bcd04898ea..5d9859c19b 100644 --- a/docker/configs/settings_local.py +++ b/docker/configs/settings_local.py @@ -8,7 +8,7 @@ from ietf.settings_postgresqldb import DATABASES # pyflakes:ignore IDSUBMIT_IDNITS_BINARY = "/usr/local/bin/idnits" -IDSUBMIT_STAGING_PATH = "test/staging/" +IDSUBMIT_STAGING_PATH = "/assets/www6s/staging/" AGENDA_PATH = '/assets/www6s/proceedings/' MEETINGHOST_LOGO_PATH = AGENDA_PATH @@ -53,7 +53,7 @@ FTP_DIR = '/assets/ftp' NOMCOM_PUBLIC_KEYS_DIR = 'data/nomcom_keys/public_keys/' -SLIDE_STAGING_PATH = 'test/staging/' +SLIDE_STAGING_PATH = '/assets/www6s/staging/' DE_GFM_BINARY = '/usr/local/bin/de-gfm' diff --git a/docker/scripts/app-create-dirs.sh b/docker/scripts/app-create-dirs.sh index b75c57767d..50431f4793 100755 --- a/docker/scripts/app-create-dirs.sh +++ b/docker/scripts/app-create-dirs.sh @@ -1,13 +1,6 @@ #!/bin/bash for sub in \ - test/id \ - test/staging \ - test/archive \ - test/rfc \ - test/media \ - test/wiki/ietf \ - data/nomcom_keys/public_keys \ /assets/archive/id \ /assets/collection \ /assets/collection/draft-archive \ @@ -27,6 +20,7 @@ for sub in \ /assets/ietfdata/derived \ /assets/ietfdata/derived/bibxml \ /assets/ietfdata/derived/bibxml/bibxml-ids \ + /assets/ietfdata/doc/draft/repository \ /assets/www6s \ /assets/www6s/staging \ /assets/www6s/wg-descriptions \ diff --git a/ietf/meeting/tests_js.py b/ietf/meeting/tests_js.py index 6199ed7eb5..b15aa70e73 100644 --- a/ietf/meeting/tests_js.py +++ b/ietf/meeting/tests_js.py @@ -5,7 +5,7 @@ import time import datetime import shutil -import os +import tempfile import re from django.utils import timezone @@ -939,13 +939,8 @@ def tearDown(self): def tempdir(self, label): # Borrowed from test_utils.TestCase slug = slugify(self.__class__.__name__.replace('.','-')) - dirname = "tmp-{label}-{slug}-dir".format(**locals()) - if 'VIRTUAL_ENV' in os.environ: - dirname = os.path.join(os.environ['VIRTUAL_ENV'], dirname) - path = os.path.abspath(dirname) - if not os.path.exists(path): - os.mkdir(path) - return path + suffix = "-{label}-{slug}-dir".format(**locals()) + return tempfile.mkdtemp(suffix=suffix) def displayed_interims(self, groups=None): sessions = add_event_info_to_session_qs( diff --git a/ietf/person/factories.py b/ietf/person/factories.py index 2247fa9b2b..2012483c0d 100644 --- a/ietf/person/factories.py +++ b/ietf/person/factories.py @@ -8,7 +8,7 @@ import faker.config import os import random -import shutil +from PIL import Image from unidecode import unidecode from unicodedata import normalize @@ -103,10 +103,9 @@ def default_photo(obj, create, extracted, **kwargs): # pylint: disable=no-self-a media_name = "%s/%s.jpg" % (settings.PHOTOS_DIRNAME, photo_name) obj.photo = media_name obj.photo_thumb = media_name - photosrc = os.path.join(settings.TEST_DATA_DIR, "profile-default.jpg") photodst = os.path.join(settings.PHOTOS_DIR, photo_name + '.jpg') - if not os.path.exists(photodst): - shutil.copy(photosrc, photodst) + img = Image.new('RGB', (200, 200)) + img.save(photodst) def delete_file(file): os.unlink(file) atexit.register(delete_file, photodst) diff --git a/ietf/settings_test.py b/ietf/settings_test.py index 024512a8db..94ca22c71b 100755 --- a/ietf/settings_test.py +++ b/ietf/settings_test.py @@ -9,9 +9,12 @@ # ./manage.py test --settings=settings_test doc.ChangeStateTestCase # -import os +import atexit +import os +import shutil +import tempfile from ietf.settings import * # pyflakes:ignore -from ietf.settings import TEST_CODE_COVERAGE_CHECKER, BASE_DIR, PHOTOS_DIRNAME +from ietf.settings import TEST_CODE_COVERAGE_CHECKER import debug # pyflakes:ignore debug.debug = True @@ -48,11 +51,20 @@ def __getitem__(self, item): if TEST_CODE_COVERAGE_CHECKER and not TEST_CODE_COVERAGE_CHECKER._started: # pyflakes:ignore TEST_CODE_COVERAGE_CHECKER.start() # pyflakes:ignore -NOMCOM_PUBLIC_KEYS_DIR=os.path.abspath("tmp-nomcom-public-keys-dir") -MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), 'test/media/') # pyflakes:ignore -MEDIA_URL = '/test/media/' -PHOTOS_DIR = MEDIA_ROOT + PHOTOS_DIRNAME # pyflakes:ignore +def tempdir_with_cleanup(**kwargs): + """Utility to create a temporary dir and arrange cleanup""" + _dir = tempfile.mkdtemp(**kwargs) + atexit.register(shutil.rmtree, _dir) + return _dir + + +NOMCOM_PUBLIC_KEYS_DIR = tempdir_with_cleanup(suffix="-nomcom-public-keys-dir") + +MEDIA_ROOT = tempdir_with_cleanup(suffix="-media") +PHOTOS_DIRNAME = "photo" +PHOTOS_DIR = os.path.join(MEDIA_ROOT, PHOTOS_DIRNAME) +os.mkdir(PHOTOS_DIR) # Undo any developer-dependent middleware when running the tests MIDDLEWARE = [ c for c in MIDDLEWARE if not c in DEV_MIDDLEWARE ] # pyflakes:ignore diff --git a/ietf/utils/test_utils.py b/ietf/utils/test_utils.py index ba35665a8d..86c5a0c1c3 100644 --- a/ietf/utils/test_utils.py +++ b/ietf/utils/test_utils.py @@ -34,7 +34,7 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import os +import tempfile import re import email import html5lib @@ -239,13 +239,8 @@ def normalize(x): def tempdir(self, label): slug = slugify(self.__class__.__name__.replace('.','-')) - dirname = "tmp-{label}-{slug}-dir".format(**locals()) - if 'VIRTUAL_ENV' in os.environ: - dirname = os.path.join(os.environ['VIRTUAL_ENV'], dirname) - path = os.path.abspath(dirname) - if not os.path.exists(path): - os.mkdir(path) - return path + suffix = "-{label}-{slug}-dir".format(**locals()) + return tempfile.mkdtemp(suffix=suffix) def assertNoFormPostErrors(self, response, error_css_selector=".is-invalid"): """Try to fish out form errors, if none found at least check the @@ -306,7 +301,7 @@ def setUp(self): # Replace settings paths with temporary directories. self._ietf_temp_dirs = {} # trashed during tearDown, DO NOT put paths you care about in this - for setting in self.settings_temp_path_overrides: + for setting in set(self.settings_temp_path_overrides): self._ietf_temp_dirs[setting] = self.tempdir(slugify(setting)) self._ietf_saved_context = django.test.utils.override_settings(**self._ietf_temp_dirs) self._ietf_saved_context.enable() diff --git a/media/.gitignore b/media/.gitignore deleted file mode 100644 index dfa8ca37ce..0000000000 --- a/media/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/floor diff --git a/media/photo/nopictureavailable.jpg b/media/photo/nopictureavailable.jpg deleted file mode 100644 index 0895f9f57c74c6199a8eed323d71fe1caae52c9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1618 zcmeH|+dmTu0LM3)k;R%}$~pEytF;rIK#{T{wAzdTR(6Uqv@3IG5=8S8}c`bFZuk(c|` z37l)*0KhioXnT84ti3(dGco>jbnGbrz`CG-5FDjP&_s16MH=lH!^a-VId>PWyjQu@ z=THr+BV%?J5l6=&rVM#R@#nD{Psgk+J2z`Q96Zx3mMb;;pR{K}jlZf+qgdwG6!uO{ zK#ftX?-Wnom0Er|(y~0dqj~Ps=QLrJ;8yDAN09;$?@_%Uy+jiTuDEBa4d*1~7SWEG z6jO7nwdHDjaS)T>I#(HlhvNAMf%EpgsUdG)&2BLo+Z`cV(6jpCtsn0y(u#mz?hrM7-%sLZuf(pZ);jyrh8$_!!!CwW8+(-yM}C7WZF= zRJ+CS>v@ZRhFAVETiu)>2-{+idZKcKq`o`jrs>S(#*nYoIlT6F;?h&jEzp3S)3_~5cZI+uzN!Z@b1aVzdKe9vS zp5*@a!vSk+$Zg$u9f1|ALx~a+{?;0Iartn?NNNg3y74&0NlZ>;9Qm}A#uHNMrrV`| zUoWF*^;2jWHIBAoE@!KG##v2PCq{P>n_+hKFO@*^_rauE-nfgBw$+5b3z)x=W$U0vN7n?|9 zZokX;;JezMI27=_v&FSIYv{$8`GBa&D6-*v3EJ zOvRCT9kda%?xW*gfMJR#qFffNYaiKobFgca{ER$G9wYYz%akBTs@}5~%o6mILI%Y# zLfwG*RB+19_Q)mr8K$#=QLNT(aAGfF0T%{e+ovQSVTYT{qBrs-V21gvV-S@H{f~Df zL2NT0V4kE|M&!0dAb2k&l15Z>P&`U-0duX!wbMXTYFc#XQ| z30u|N*4pH1l8i(Oc~uwJ;|JFv(c+75;IOA3qaS)%o;L^+d2Mh`y9wxNS$h# z?tzpzr76Cf__iW`vH`!4zB#cp)1SoTnr1$*LRLMQ7o2;II{u>p5Lu7`tY)MUJVUqQ z0RR~Q7Gvvu0r;uZLZR=V=D^fd{(2^A2py^jjCW{&8()!)FE06#D%d1{JvP|QwXj+; zY|r9f@Hd}iV#4az7686bOo)NGF$DB2}c8==|f?7-9&`9RxkOH`QrTlKbV z`4>C<)mn^VLF%_PTA;)elM04T`-rx~h4-~E#qPt|#gH%)8no*Fq5r@UKi@Jb9qAz3 erH0!=i&Q7I<(v2jXU|_>0l+%AWA537X8#0-_TYm6 diff --git a/media/photo/profile-default.jpg b/media/photo/profile-default.jpg deleted file mode 100644 index d6b03e100415a0fa9effad25c793f33957929e2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1397 zcmex=>ukC3pCfH06P05XITq?4J21E^7eo0A(TN+S4wfI*Oh zA%!7@nNf*>Nsy6Qkn#T!25GQUnSsuQ0}gg37NE<3k~RVijLbkcb24+YLKHAEFfjuq z*##916FG#0B^(2Tr#w_DY!neSG7m~hE-G2LQQ6pe;^vD$-6F__LQDsV3L;qr6}`p4 z!wfW8kXewyp5ekpK}JCV!P>Ier_+?rbWdKHE7*~#p#0QyQ}e|`{OR`!-(>uFVYbSi z_fx^?Y8{@R=lZJ8ddOYb8nkom^=1i&%Z=CXH95KFgg%)Tdd($qGTX%sg3C5qe%w(L z^&rfD#+(`RU43>?GDyA zi-2ZxuAlNP?sD`i&5v(aF-m8r{5D5cMQ!(_%4O9t>yW>v~RuU`$cX| zNhwBKzKfkX{3pv!OW(o8g~bI(ur4nu_;qdjzj@35xu$7*Joy+ZYLL*YKNsA;djUZg*#PcpextM7Qv@%!X?FPsG&04%Owb|~v@3`+>FnaqFt^rGa3S*v`bS?p)t z{lm7riGOx>%y+h}OD2^t@Ge=qSl7}bZX>^I`LVWW-yJWWU1nVBbGXC)w%mV)?1p>W zc($GRqun4aTeC*o`+(G}lQT>@{t26#-`{$tTCA`6?BO4I$6IqZ-YWRJO-d``yOG)3 z!ry+@cPb`%eVHgAD99E+Gm^W_@Vs~NHnu2zy{pG6X8zT@`=dv8`CHz-zupCXGdwV3 zK2uCx-jTBF2V(qPjcZq=F>md^Q+xY(2G_?}>8pJgJJx=>l|Su;cBY!#ouKW3?`0SC z9WrZ?pTAX5!f5^i+ygEl6=8`h6x3~;%6paa#b<1l3ZL;WO#I4 zc3bO`e3mj1nLt6t=$+;wfvIh>f^OH?15J<5GP!zWuK%7Dof+SF{+*FAPg}g-RVtd+rSaJ=Std8L zR)nwIB=cNZd**v>ukC3pCfH06P05XITq?4J21E^7eo0A(TN+S4wfI*Oh zA%!7@nNf*>Nsy6Qkn#T!25GQUnSsuQ0}gg37NE<3k~RVijLbkcb24+YLKHAEFfjuq z*##916FG#0B^(2Tr#w_DY!neSG7m~hE-G2LQQ6pe;^vD$-6F__LQDsV3L;qr6}`p4 z!wfW8kXewyp5ekpK}JCV!P>Ier_+?rbWdKHE7*~#p#0QyQ}e|`{OR`!-(>uFVYbSi z_fx^?Y8{@R=lZJ8ddOYb8nkom^=1i&%Z=CXH95KFgg%)Tdd($qGTX%sg3C5qe%w(L z^&rfD#+(`RU43>?GDyA zi-2ZxuAlNP?sD`i&5v(aF-m8r{5D5cMQ!(_%4O9t>yW>v~RuU`$cX| zNhwBKzKfkX{3pv!OW(o8g~bI(ur4nu_;qdjzj@35xu$7*Joy+ZYLL*YKNsA;djUZg*#PcpextM7Qv@%!X?FPsG&04%Owb|~v@3`+>FnaqFt^rGa3S*v`bS?p)t z{lm7riGOx>%y+h}OD2^t@Ge=qSl7}bZX>^I`LVWW-yJWWU1nVBbGXC)w%mV)?1p>W zc($GRqun4aTeC*o`+(G}lQT>@{t26#-`{$tTCA`6?BO4I$6IqZ-YWRJO-d``yOG)3 z!ry+@cPb`%eVHgAD99E+Gm^W_@Vs~NHnu2zy{pG6X8zT@`=dv8`CHz-zupCXGdwV3 zK2uCx-jTBF2V(qPjcZq=F>md^Q+xY(2G_?}>8pJgJJx=>l|Su;cBY!#ouKW3?`0SC z9WrZ?pTAX5!f5^i+ygEl6=8`h6x3~;%6paa#b<1l3ZL;WO#I4 zc3bO`e3mj1nLt6t=$+;wfvIh>f^OH?15J<5GP!zWuK%7Dof+SF{+*FAPg}g-RVtd+rSaJ=Std8L zR)nwIB=cNZd**v