internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: carpentries/amy

Issue:
Different colour scheme for test-amy / local builds
It'd be useful to have a visual indicator of which site you're on - it's currently quite easy to mix up production AMY and staging/local AMY pages if you have both open.

I think the nav background colour would be the easiest and most visually obvious thing to change.


Discussion:
Thanks for opening this issue @elichad 
In the interest of accessiblity and to make it clear what the different color means, we may also want to add a non-color cue. Perhaps an additional div in the navbar as a reminder that you are in the testing environment? 

I am not at all bound to these colors or text; just an example of what I was thinking of:

![image](https://user-images.githubusercontent.com/829690/215091801-3150797e-42e7-4561-958a-664e557e5e18.png)

This would require some conditional in the build process (i.e., ```if local or test then css....```)





Good idea!
Update to say this should also apply to the login screen, so it is clear to users whether they are logging in to the test version or the real version. 

Resolving patch:
diff --git a/amy/workshops/context_processors.py b/amy/workshops/context_processors.py
--- a/amy/workshops/context_processors.py
+++ b/amy/workshops/context_processors.py
@@ -1,3 +1,4 @@
+from django.conf import settings
 from django.http import HttpRequest
 
 from amy import __version__
@@ -6,3 +7,8 @@
 def version(request: HttpRequest) -> dict:
     data = {"amy_version": __version__}
     return data
+
+
+def site_banner(request: HttpRequest) -> dict:
+    data = {"SITE_BANNER_STYLE": settings.SITE_BANNER_STYLE}
+    return data
diff --git a/config/settings.py b/config/settings.py
--- a/config/settings.py
+++ b/config/settings.py
@@ -44,6 +44,7 @@
         "https://workshop-reports.carpentries.org/?key={hash}&slug={slug}",
     ),
     AMY_INSTRUCTOR_RECRUITMENT_ENABLED=(bool, False),
+    AMY_SITE_BANNER=(str, "local"),  # should be "local", "testing", or "production"
 )
 
 # OS environment variables take precedence over variables from .env
@@ -362,6 +363,7 @@
                 "django.contrib.messages.context_processors.messages",
                 # AMY version
                 "workshops.context_processors.version",
+                "workshops.context_processors.site_banner",
                 # Consent enums
                 "consents.context_processors.terms",
                 # GitHub auth
@@ -610,3 +612,13 @@
 # -----------------------------------------------------------------------------
 # Settings for Instructor Recruitment project
 INSTRUCTOR_RECRUITMENT_ENABLED = env("AMY_INSTRUCTOR_RECRUITMENT_ENABLED")
+
+# Site banner style
+# -----------------------------------------------------------------------------
+# This should show a special banner on all sites so that users are aware of
+# local/dev/test stage they are using.
+SITE_BANNER_STYLE = env("AMY_SITE_BANNER")
+if SITE_BANNER_STYLE not in ("local", "testing", "production"):
+    raise ImproperlyConfigured(
+        "SITE_BANNER_STYLE accepts only one of 'local', 'testing', 'production'."
+    )


Test patch:
Repository: carpentries/amy

Issue:
Consents: display new consents in profile editing history
The editing history of a person's profile (the `/workshops/version/` endpoint, accessed by clicking on `Last modified` on the top of the profile) doesn't seem to display the values of the new consents system, only the old one.


Discussion:
@elichad This is another ticket you can work on. I don't think there's an easy solution to displaying changes made to the new style consents, since they are following a relationship to a person, so I would suggest that you start with investigation first. I also think the other ticket #2302 is more important.

Resolving patch:
diff --git a/amy/workshops/views.py b/amy/workshops/views.py
--- a/amy/workshops/views.py
+++ b/amy/workshops/views.py
@@ -621,6 +621,9 @@ def get_context_data(self, **kwargs):
                 "tasks": self.object.task_set.select_related("role", "event").order_by(
                     "-event__slug"
                 ),
+                "consents": self.object.consent_set.select_related("term").order_by(
+                    "-archived_at"
+                ),
                 "consents_form": ActiveTermConsentsForm(
                     form_tag=False,
                     prefix="consents",


Test patch:
Repository: carpentries/amy

Issue:
Add  filter for default training requests view 
The [training requests view](https://amy.carpentries.org/requests/training_requests/) returns all training requests (currently over 7000 results).  This makes the page load very slowly (possibly related to #2314).

Default search results should be:
* State:  Pending or accepted
* Is Matched: Unmatched 

We should then be able to adjust search as we currently do.

Edit to note in effect, the link to Training requests should go [here](https://amy.carpentries.org/requests/training_requests/?search=&group_name=&state=no_d&matched=u&affiliation=&location=&order_by=) instead of [here](https://amy.carpentries.org/requests/training_requests/).


Discussion:

Resolving patch:
diff --git a/amy/extrequests/filters.py b/amy/extrequests/filters.py
--- a/amy/extrequests/filters.py
+++ b/amy/extrequests/filters.py
@@ -2,6 +2,7 @@
 
 from django.db.models import Q
 from django.forms import widgets
+from django.http import QueryDict
 import django_filters
 
 from extrequests.models import SelfOrganisedSubmission, WorkshopInquiryRequest
@@ -22,6 +23,16 @@
 
 
 class TrainingRequestFilter(AMYFilterSet):
+    def __init__(self, data=None, *args, **kwargs):
+        # If no filters are set, use some default settings.
+        # This avoids handling the full list of training requests
+        # client-side unless the user deliberately chooses to do so.
+        # See https://github.com/carpentries/amy/issues/2314
+        if not data:
+            data = QueryDict("state=no_d&matched=u")
+
+        super().__init__(data, *args, **kwargs)
+
     search = django_filters.CharFilter(
         label="Name or Email",
         method="filter_by_person",


Test patch:
diff --git a/amy/extrequests/tests/test_training_request.py b/amy/extrequests/tests/test_training_request.py
--- a/amy/extrequests/tests/test_training_request.py
+++ b/amy/extrequests/tests/test_training_request.py
@@ -290,7 +290,32 @@ def setUp(self):
         self.second_training.tags.add(self.ttt)
 
     def test_view_loads(self):
+        """
+        View should default to settings:
+            state=no_d (Pending or accepted)
+            matched=u (Unmatched)
+        """
+        # Act
         rv = self.client.get(reverse("all_trainingrequests"))
+
+        # Assert
+        self.assertEqual(rv.status_code, 200)
+        self.assertEqual(
+            set(rv.context["requests"]),
+            {self.second_req},
+        )
+
+    def test_view_loads_all_on_request(self):
+        """
+        Explicitly setting state and matched to null should return all requests.
+        """
+        # Arrange
+        query_string = "state=&matched="
+
+        # Act
+        rv = self.client.get(reverse("all_trainingrequests"), QUERY_STRING=query_string)
+
+        # Assert
         self.assertEqual(rv.status_code, 200)
         self.assertEqual(
             set(rv.context["requests"]),
Repository: carpentries/amy

Issue:
Errors in forms on test server 
When filling out https://test-amy2.carpentries.org/forms/request_training/ 
we get the error:

```
AnymailRequestsAPIError at /forms/request_training/
Mailgun API response 401 (Unauthorized): 'Forbidden'
```

While the email should not go out, we should still be able to create forms in the test environment.


Discussion:
This may be addressed in the new email automation project, because we will have ways to test emails.
@pbanaszkiewicz We are adding this to the emails project as you are testing out how to send emails from the test server. 
@pbanaszkiewicz  @elichad  I would like to see if we can address this error sooner than the release of automated emails features.

One of our next projects is validating member codes on the instructor training application and the workshop request forms.   We will need to be able to successfully submit test forms in order to do user testing for these features.  I'm ok with a temporary fix, but we do need to be able to conduct user testing.
@maneesha My initial idea is that this is caused by us using fake keys for Mailgun, instead of turning off the feature that sends the emails upon submitting the form. I'll check if we can have sandbox-like keys for the test server, and if not then I'll conditionally disable the email sending for the forms that use it.

Resolving patch:
diff --git a/amy/workshops/base_views.py b/amy/workshops/base_views.py
--- a/amy/workshops/base_views.py
+++ b/amy/workshops/base_views.py
@@ -1,6 +1,7 @@
 from smtplib import SMTPException
 from typing import Optional
 
+from anymail.exceptions import AnymailRequestsAPIError
 from django.conf import settings
 from django.contrib import messages
 from django.contrib.auth.mixins import PermissionRequiredMixin
@@ -342,7 +343,7 @@ def autoresponder(self, form, fail_silently=True):
 
         try:
             email.send()
-        except SMTPException as e:
+        except (SMTPException, AnymailRequestsAPIError) as e:
             if not fail_silently:
                 raise e
 
diff --git a/config/settings.py b/config/settings.py
--- a/config/settings.py
+++ b/config/settings.py
@@ -429,7 +429,7 @@
     not ANYMAIL["MAILGUN_API_KEY"] or not ANYMAIL["MAILGUN_SENDER_DOMAIN"]
 ):
     raise ImproperlyConfigured(
-        "Mailgun settings are required when running " "with DEBUG=False."
+        "Mailgun settings are required when running with DEBUG=False."
     )
 
 


Test patch:
Repository: carpentries/amy

Issue:
Test-amy2: update login through github
It's still set up for test-amy.


Discussion:
@maneesha @froggleston I believe the OAuth application entry was transferred from my account over to Carpentries. I cannot manage it myself.

You should be able to find it here:
https://github.com/settings/developers

![obraz](https://user-images.githubusercontent.com/72821/230647388-548d9ba8-c1ff-4543-bb87-bcd272eea134.png)

It's enough to update OAuth application domain to test-amy2, but if it changes the authorization token(s) please let me know.

Hey @pbanaszkiewicz - do you have the configuration for the old app that I can work from? DM me in slack if there's sensitive info.
@froggleston Did we resolve this issue or not? I don't remember.

Resolving patch:
diff --git a/config/settings.py b/config/settings.py
--- a/config/settings.py
+++ b/config/settings.py
@@ -197,6 +197,7 @@
 # Github API token (optional). Setting this token reduces limits and quotes
 # on Github API.
 GITHUB_API_TOKEN = env("AMY_GITHUB_API_TOKEN")
+SOCIAL_AUTH_REDIRECT_IS_HTTPS = True
 SOCIAL_AUTH_PIPELINE = (
     "social_core.pipeline.social_auth.social_details",
     "social_core.pipeline.social_auth.social_uid",


Test patch:
Repository: carpentries/amy

Issue:
Allow Instructors to indicate accommodations they need to teach effectively
Suggested by @bkmgit in #2435.

An Instructor can currently provide information about accommodations in the notes when expressing interest in teaching a workshop, but we don't explicitly ask for this anywhere. We could make this part of the instructor's AMY profile to avoid them having to provide the same information repeatedly.


Discussion:
This needs some discussion about what exact information we should ask for and store, how we handle temporary vs permanent accommodations, and what control instructors should have over sharing this information with workshop hosts.

As a simple improvement, we can update the text on the sign-up form to explicitly ask the instructor to include any accommodations they need in the notes.
Marking this high priority for the small change (updating text on sign-up form).  Will consider other improvements later.

Resolving patch:
diff --git a/amy/dashboard/forms.py b/amy/dashboard/forms.py
--- a/amy/dashboard/forms.py
+++ b/amy/dashboard/forms.py
@@ -302,7 +302,9 @@ class SignupForRecruitmentForm(forms.ModelForm):
         required=False,
         widget=forms.Textarea,
         label="Your notes",
-        help_text="Is there anything else you would like to share with us?",
+        help_text="Is there anything else you would like to share with us? "
+        "Please include any accommodations that the host organization should provide "
+        "to enable you to teach effectively.",
     )
     helper = BootstrapHelper(
         submit_label="Submit my interest in teaching this workshop",


Test patch:
Repository: carpentries/amy

Issue:
[Emails] Consider `active` field on email templates
As a user I want to be able to edit field `active` on email templates.

As a system I need to consider `active` field on email templates when they are used by the email controller.


Discussion:

Resolving patch:
diff --git a/amy/emails/forms.py b/amy/emails/forms.py
--- a/amy/emails/forms.py
+++ b/amy/emails/forms.py
@@ -21,6 +21,7 @@ class Meta:
         model = EmailTemplate
         fields = [
             "name",
+            "active",
             "signal",
             "from_header",
             "reply_to_header",


Test patch:
diff --git a/amy/emails/tests/test_controller.py b/amy/emails/tests/test_controller.py
--- a/amy/emails/tests/test_controller.py
+++ b/amy/emails/tests/test_controller.py
@@ -55,6 +55,31 @@ def test_schedule_email__no_template(self) -> None:
                 to_header=["harry@potter.com"],
             )
 
+    def test_schedule_email__inactive_template(self) -> None:
+        # Arrange
+        now = timezone.now()
+        signal = "test_email_template"
+        EmailTemplate.objects.create(
+            active=False,
+            name="Test Email Template",
+            signal=signal,
+            from_header="workshops@carpentries.org",
+            cc_header=["team@carpentries.org"],
+            bcc_header=[],
+            # invalid Django template syntax
+            subject="Greetings",
+            body="Hello, {{ name }}! Nice to meet **you**.",
+        )
+
+        # Act & Assert
+        with self.assertRaises(EmailTemplate.DoesNotExist):
+            EmailController.schedule_email(
+                signal,
+                context={"name": "Harry"},
+                scheduled_at=now,
+                to_header=["harry@potter.com"],
+            )
+
     def test_schedule_email__invalid_template(self) -> None:
         # Arrange
         now = timezone.now()
Repository: carpentries/amy

Issue:
Integrity error when submitting instructor training application
If I enter a non-Eventbrite URL such as https://google.com, I get the warning that it must be an Evenbrite URL.  If I try an Eventbrite URL such as one listed on our [calendar](https://carpentries.github.io/instructor-training/training_calendar.html) or if I leave that blank, I get an Integrity Error.  
Integrity error on `eventbrite_url` field when submitting Instructor Training form: 

![image](https://github.com/carpentries/amy/assets/829690/982108de-1bfd-4ddf-960c-6c41e616121a)



Discussion:

Resolving patch:
diff --git a/amy/extforms/forms.py b/amy/extforms/forms.py
--- a/amy/extforms/forms.py
+++ b/amy/extforms/forms.py
@@ -34,6 +34,11 @@ class TrainingRequestForm(forms.ModelForm):
 
     helper = BootstrapHelper(wider_labels=True, add_cancel_button=False)
 
+    code_of_conduct_agreement = forms.BooleanField(
+        required=True,
+        label=TrainingRequest._meta.get_field("code_of_conduct_agreement").verbose_name,
+    )
+
     class Meta:
         model = TrainingRequest
         fields = (
@@ -257,6 +262,7 @@ def clean_eventbrite_url(self):
         eventbrite_url = self.cleaned_data.get("eventbrite_url", "")
         if eventbrite_url and "eventbrite" not in urlparse(eventbrite_url).hostname:
             raise ValidationError("Must be an Eventbrite URL.")
+        return eventbrite_url
 
     def clean(self):
         super().clean()


Test patch:
diff --git a/amy/extforms/tests/test_training_request_form.py b/amy/extforms/tests/test_training_request_form.py
--- a/amy/extforms/tests/test_training_request_form.py
+++ b/amy/extforms/tests/test_training_request_form.py
@@ -26,6 +26,7 @@ def setUp(self):
         self.data = {
             "review_process": "preapproved",
             "member_code": "coolprogrammers",
+            "eventbrite_url": "https://www.eventbrite.com/e/711576483417",
             "personal": "John",
             "family": "Smith",
             "email": "john@smith.com",
@@ -510,3 +511,20 @@ def test_eventbrite_url_validation__valid(self):
         # Assert
         self.assertEqual(rv.status_code, 200)
         self.assertNotContains(rv, self.INVALID_EVENTBRITE_URL_ERROR)
+
+    def test_coc_agreement_required(self):
+        """Should error if CoC checkbox is not ticked."""
+        # Arrange
+        self.setUpMembership()
+        data = {"code_of_conduct_agreement": "false"}
+
+        # Act
+        rv = self.client.post(reverse("training_request"), data=data)
+        errors = dict(rv.context["form"].errors)
+
+        # Assert
+        self.assertEqual(rv.status_code, 200)
+        self.assertIn("code_of_conduct_agreement", errors)
+        self.assertListEqual(
+            errors["code_of_conduct_agreement"], ["This field is required."]
+        )
Repository: carpentries/amy

Issue:
Scheduled/completed emails should be visible from the related object
For example, this [membership onboarding email](https://test-amy2.carpentries.org/emails/scheduled_email/aa440d84-dff4-49ac-8f33-a9419891a254/) should be visible from [this related membership object](https://test-amy2.carpentries.org/fiscal/membership/47/).

We would want to do this when the related object is a membership or an event. We may not want to do this when the related object is a person.  


Discussion:
Copying in notes from duplicate issue #2627: 

> For events, memberships, and persons, admins should see all associated scheduled/completed/failed emails in that object's view. For example, for a "post workshop email" that email should be listed in the event view and the person view for each person receiving that email.
> 
> For persons, the person should not see those emails in their own view. Currently other objects (event, membership) can not be viewed by a non-admin user but the same constraint should be in place.
@maneesha As far as I know, non-admin users don't have access to administrative pages for persons. Please correct me if I'm wrong.
@maneesha I added the display of related scheduled emails to following models:
* Event
* Person
* Membership

There are models used as related to the scheduled emails, but we have no place to display the list of scheduled emails:
* Award
* Instructor Recruitment Signup

@maneesha let me know if you want to add that list of related scheduled emails to any of those 2 remaining models, you can also tell me where you want that list displayed.

Resolving patch:
diff --git a/amy/emails/templatetags/emails.py b/amy/emails/templatetags/emails.py
--- a/amy/emails/templatetags/emails.py
+++ b/amy/emails/templatetags/emails.py
@@ -1,6 +1,12 @@
 from django import template
+from django.contrib.contenttypes.models import ContentType
+from django.db.models import Model, QuerySet
 
-from emails.models import ScheduledEmailStatus, ScheduledEmailStatusActions
+from emails.models import (
+    ScheduledEmail,
+    ScheduledEmailStatus,
+    ScheduledEmailStatusActions,
+)
 
 register = template.Library()
 
@@ -12,3 +18,12 @@ def allowed_actions_for_status(status: ScheduledEmailStatus) -> list[str]:
         for key, statuses in ScheduledEmailStatusActions.items()
         if status in statuses
     ]
+
+
+@register.simple_tag
+def get_related_scheduled_emails(obj: Model) -> QuerySet[ScheduledEmail]:
+    content_type = ContentType.objects.get_for_model(obj.__class__)
+    return ScheduledEmail.objects.filter(
+        generic_relation_content_type=content_type,
+        generic_relation_pk=obj.pk,
+    ).order_by("-created_at")
diff --git a/config/settings.py b/config/settings.py
--- a/config/settings.py
+++ b/config/settings.py
@@ -46,7 +46,7 @@
     ),
     AMY_SITE_BANNER=(str, "local"),  # should be "local", "testing", or "production"
     # Feature flags
-    AMY_INSTRUCTOR_RECRUITMENT_ENABLED=(bool, False),
+    AMY_INSTRUCTOR_RECRUITMENT_ENABLED=(bool, True),
 )
 
 # OS environment variables take precedence over variables from .env


Test patch:
Repository: jschneier/django-storages

Issue:
modified_time fails for new file
see also #11 and [the original issue](https://bitbucket.org/david/django-storages/issue/163/s3boto-modified_time-fails-for-new-file)

i opened a bug boto/boto#2841



Discussion:

Resolving patch:
diff --git a/storages/backends/s3boto.py b/storages/backends/s3boto.py
--- a/storages/backends/s3boto.py
+++ b/storages/backends/s3boto.py
@@ -1,6 +1,7 @@
 import os
 import posixpath
 import mimetypes
+from datetime import datetime
 from gzip import GzipFile
 from tempfile import SpooledTemporaryFile
 import warnings
@@ -15,7 +16,7 @@
     from boto.s3.connection import S3Connection, SubdomainCallingFormat
     from boto.exception import S3ResponseError
     from boto.s3.key import Key as S3Key
-    from boto.utils import parse_ts
+    from boto.utils import parse_ts, ISO8601
 except ImportError:
     raise ImproperlyConfigured("Could not load Boto's S3 bindings.\n"
                                "See https://github.com/boto/boto")
@@ -410,6 +411,7 @@ def _save(self, name, content):
             key = self.bucket.new_key(encoded_name)
         if self.preload_metadata:
             self._entries[encoded_name] = key
+            key.last_modified = datetime.utcnow().strftime(ISO8601)
 
         key.set_metadata('Content-Type', content_type)
         self._save_content(key, content, headers=headers)


Test patch:
diff --git a/tests/test_s3boto.py b/tests/test_s3boto.py
--- a/tests/test_s3boto.py
+++ b/tests/test_s3boto.py
@@ -9,6 +9,7 @@
 from django.core.files.base import ContentFile
 
 from boto.s3.key import Key
+from boto.utils import parse_ts, ISO8601
 
 from storages.compat import urlparse
 from storages.backends import s3boto
@@ -281,3 +282,14 @@ def test_generated_url_is_encoded(self):
         self.assertEqual(parsed_url.path,
                          "/whacky%20%26%20filename.mp4")
 
+    def test_new_file_modified_time(self):
+        self.storage.preload_metadata = True
+        name = 'test_storage_save.txt'
+        content = ContentFile('new content')
+        utcnow = datetime.datetime.utcnow()
+        with mock.patch('storages.backends.s3boto.datetime') as mock_datetime:
+            mock_datetime.utcnow.return_value = utcnow
+            self.storage.save(name, content)
+            self.assertEqual(self.storage.modified_time(name),
+                             parse_ts(utcnow.strftime(ISO8601)))
+