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

Issue:
Membership.contribution_type should not allow blank values
Look at `models.py`:
```python
class Membership(models.Model):
    ...
    CONTRIBUTION_CHOICES = (
        ('financial', 'Financial'),
        ('person-days', 'Person-days'),
        ('other', 'Other'),
    )
    contribution_type = models.CharField(
        max_length=STR_MED, blank=True,
        choices=CONTRIBUTION_CHOICES,
    )
```

That means that `contribution_type` can be blank. @gvwilson Is it intended?


Discussion:
It shouldn't be blank - "other" covers that case.


Resolving patch:
diff --git a/workshops/migrations/0121_merge.py b/workshops/migrations/0121_merge.py
new file mode 100644
--- /dev/null
+++ b/workshops/migrations/0121_merge.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.5 on 2016-11-01 19:41
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('workshops', '0120_auto_20161023_1345'),
+        ('workshops', '0120_merge'),
+    ]
+
+    operations = [
+    ]
diff --git a/workshops/migrations/0122_auto_20161101_1441.py b/workshops/migrations/0122_auto_20161101_1441.py
new file mode 100644
--- /dev/null
+++ b/workshops/migrations/0122_auto_20161101_1441.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.5 on 2016-11-01 19:41
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('workshops', '0121_merge'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='membership',
+            name='contribution_type',
+            field=models.CharField(choices=[('financial', 'Financial'), ('person-days', 'Person-days'), ('other', 'Other')], max_length=40),
+        ),
+    ]
diff --git a/workshops/models.py b/workshops/models.py
--- a/workshops/models.py
+++ b/workshops/models.py
@@ -103,7 +103,7 @@ class Membership(models.Model):
         ('other', 'Other'),
     )
     contribution_type = models.CharField(
-        max_length=STR_MED, blank=True,
+        max_length=STR_MED, null=False, blank=False,
         choices=CONTRIBUTION_CHOICES,
     )
     workshops_without_admin_fee_per_year = models.PositiveIntegerField(


Test patch:
Repository: carpentries/amy

Issue:
Import old release notes
Add them to `docs/releases` dictionary. Get old release notes from [Piotr's blog](http://piotr.banaszkiewicz.org/).

It'd be better to keep release notes along with source code, so that they won't get lost, and to have everything in one place. @pbanaszkiewicz what's your opinion?


Discussion:
:+1: for keeping all release notes here.


Resolving patch:
diff --git a/extforms/views.py b/extforms/views.py
--- a/extforms/views.py
+++ b/extforms/views.py
@@ -1,5 +1,6 @@
 from django.conf import settings
-from django.core.urlresolvers import reverse, reverse_lazy
+from django.contrib import messages
+from django.core.urlresolvers import reverse_lazy
 from django.shortcuts import render
 from django.template.loader import get_template
 from django.views.generic import TemplateView


Test patch:
Repository: carpentries/amy

Issue:
Incorrect title on Badge Detail page
The title of the `badge_detail` page for the badge `maintainer` is `Airport Maintainer`, i.e. `Airport` is appended to the title of each badge.

Is this the expected functionality?
Incorrect title on Badge Detail page
The title of the `badge_detail` page for the badge `maintainer` is `Airport Maintainer`, i.e. `Airport` is appended to the title of each badge.

Is this the expected functionality?


Discussion:
Nope, that's a bug.

Nope, that's a bug.


Resolving patch:
diff --git a/workshops/views.py b/workshops/views.py
--- a/workshops/views.py
+++ b/workshops/views.py
@@ -1616,7 +1616,7 @@ class BadgeDetails(OnlyForAdminsMixin, AMYDetailView):
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
 
-        context['title'] = 'Airport {0}'.format(self.object)
+        context['title'] = 'Badge {0}'.format(self.object)
         filter = BadgeAwardsFilter(
             self.request.GET,
             queryset=self.object.award_set.select_related(


Test patch:
Repository: carpentries/amy

Issue:
Re-order questions on workshop request form
Make this question:

> Which of the following applies to your payment for the administrative fee?*

come immediately after this question:

> I understand the Software Carpentry Foundation's administration fee.



Discussion:

Resolving patch:
diff --git a/workshops/models.py b/workshops/models.py
--- a/workshops/models.py
+++ b/workshops/models.py
@@ -1235,6 +1235,23 @@ class EventRequest(AssignmentMixin, ActiveMixin, CreatedUpdatedMixin,
         # verbose_name a.k.a. label and help_text were moved to the
         # SWCEventRequestForm and DCEventRequestForm
     )
+
+    ADMIN_FEE_PAYMENT_CHOICES = (
+        ('NP1', 'Non-profit / non-partner: US$2500'),
+        ('FP1', 'For-profit: US$10,000'),
+        ('self-organized', 'Self-organized: no fee (please let us know if you '
+                           'wish to make a donation)'),
+        ('waiver', 'Waiver requested (please give details in '
+                   '"Anything else")'),
+    )
+    admin_fee_payment = models.CharField(
+        max_length=STR_MED,
+        choices=ADMIN_FEE_PAYMENT_CHOICES,
+        verbose_name='Which of the following applies to your payment for the '
+                     'administrative fee?',
+        blank=False,
+        default='NP1',
+    )
     fee_waiver_request = models.BooleanField(
         help_text='Waiver\'s of the administrative fee are available on '
                   'a needs basis. If you are interested in submitting a waiver'
@@ -1267,23 +1284,6 @@ class EventRequest(AssignmentMixin, ActiveMixin, CreatedUpdatedMixin,
                      ' accommodations',
         blank=True,
     )
-
-    ADMIN_FEE_PAYMENT_CHOICES = (
-        ('NP1', 'Non-profit / non-partner: US$2500'),
-        ('FP1', 'For-profit: US$10,000'),
-        ('self-organized', 'Self-organized: no fee (please let us know if you '
-                           'wish to make a donation)'),
-        ('waiver', 'Waiver requested (please give details in '
-                   '"Anything else")'),
-    )
-    admin_fee_payment = models.CharField(
-        max_length=STR_MED,
-        choices=ADMIN_FEE_PAYMENT_CHOICES,
-        verbose_name='Which of the following applies to your payment for the '
-                     'administrative fee?',
-        blank=False,
-        default='NP1',
-    )
     comment = models.TextField(
         help_text='What else do you want us to know about your workshop? About'
                   ' your attendees? About you?',


Test patch:
Repository: carpentries/amy

Issue:
Delete button doesn't work in edit views
This is because delete views redirects to URL with appended `/delete`, i.e. `workshops/training_progress/1172/edit/delete` instead of `https://amy.software-carpentry.org/workshops/training_progress/1172/delete`.

A workaround is to delete objects from detailed view. This workaround doesn't work for TrainingProgresses, because they don't have detailed view (see #1084).


Discussion:
FWIW, I believe that the delete button should be displayed on the detail view and not the edit view. That is what I have observed most of the times.
@pbanaszkiewicz ?

I lean towards "Delete" button on the detail view (and in some cases on the list view).
@pbanaszkiewicz Do you propose we add a detail view for training progress objects?
@narayanaditya95 yes if it's not too much work. If someone is lazy, fixing just the URL on the edit view would be fine too.
That seems to be the easiest way out. +1

Resolving patch:
diff --git a/workshops/views.py b/workshops/views.py
--- a/workshops/views.py
+++ b/workshops/views.py
@@ -3088,7 +3088,7 @@ class TrainingProgressUpdate(RedirectSupportMixin, OnlyForAdminsMixin,
                              AMYUpdateView):
     model = TrainingProgress
     form_class = TrainingProgressForm
-    template_name = 'workshops/generic_form.html'
+    template_name = 'workshops/trainingprogress_form.html'
 
 
 class TrainingProgressDelete(RedirectSupportMixin, OnlyForAdminsMixin,


Test patch:
diff --git a/workshops/test/test_training_progress.py b/workshops/test/test_training_progress.py
--- a/workshops/test/test_training_progress.py
+++ b/workshops/test/test_training_progress.py
@@ -256,3 +256,16 @@ def test_delete_view_works(self):
         self.assertEqual(rv.status_code, 200)
         self.assertEqual(rv.resolver_match.view_name, 'all_trainees')
         self.assertEqual(set(TrainingProgress.objects.all()), set())
+
+    def test_delete_trainingprogress_from_edit_view(self):
+        """Regression test for issue #1085."""
+        trainingprogress_edit = self.app.get(
+            reverse('trainingprogress_edit', args=[self.progress.pk]),
+            user='admin'
+        )
+        self.assertRedirects(
+            trainingprogress_edit.forms['delete-form'].submit(),
+            reverse('all_trainees')
+        )
+        with self.assertRaises(TrainingProgress.DoesNotExist):
+            self.progress.refresh_from_db()
Repository: carpentries/amy

Issue:
Better format to select tags
Can we have a better format to select tags?  The current scrolling list makes it difficult to see which tags are selected when in 'edit' mode.



Discussion:
This could be fixed by just making the box resizable.  Then the user can make it bigger if they wanted to see the whole list at once.

@maneesha we can perhaps make all widgets for multiselection (such as tags) as long as the list of items so that all items are visible at once (ie. no resizing then), would that work?

Yes.  

This probably requires some JS magic to automatically set `options.size` to the number of items it contains.

User resizable or auto size -- what ever is easier.
Auto size might get unwieldy if we end up with fields that have many options - the widget could get really big.

I suggest we use buttons as checkboxes to select multiple tags. It is also responsive and works great with touch devices.

[Demo](http://www.bootply.com/iKosQHSrNf)

@narayanaditya95 this might be silly, but if I didn't know AMY internals and how to roll back from a backup, I'd be very afraid to click anything that resembles a button that I didn't click before ;)

From dev point of view, neither of demo'd widgets shows its function (radio or multiselection) clearly.

Yes, it may be tough to distinguish between the selected and the non-selected checkboxes.
What are your views regarding [bootstrap-switch](http://www.bootstrap-switch.org/)? They convey the purpose.

Any progress on this?  I think just having the box be resizable would meet my needs.

@maneesha, it's planned for v1.10 and we'll take care of that after releasing v1.9.

This can be easily solved by adding `size="20"` to the `<select>` widget. However, for some reason, this doesn't work:

``` python
    tag = django_filters.MultipleChoiceFilter(
        choices=TAG_CHOICES, name='tags', action=filter_tag_by_name,
        widget=SelectMultiple,
        attrs={'size': 10},
        # widget=SelectMultiple(attrs={'size': 10})  # does not work too
    )
```


Resolving patch:
diff --git a/workshops/forms.py b/workshops/forms.py
--- a/workshops/forms.py
+++ b/workshops/forms.py
@@ -10,6 +10,7 @@
 from django.core.validators import RegexValidator
 from django.forms import (
     HiddenInput,
+    SelectMultiple,
     CheckboxSelectMultiple,
     TextInput,
     modelformset_factory,
@@ -435,6 +436,7 @@ class Meta:
             'latitude': TextInput,
             'longitude': TextInput,
             'invoice_status': RadioSelect,
+            'tags': SelectMultiple(attrs={'size': 10}),
         }
 
     class Media:


Test patch:
Repository: carpentries/amy

Issue:
cancelled workshops should not show up as not invoiced in dashboard
Title says it all.
This probably relates to #1064 

cancelled workshops should not show up as not invoiced in dashboard
Title says it all.
This probably relates to #1064 



Discussion:

Resolving patch:
diff --git a/workshops/models.py b/workshops/models.py
--- a/workshops/models.py
+++ b/workshops/models.py
@@ -838,8 +838,9 @@ def uninvoiced_events(self):
         These are marked as uninvoiced, and have occurred.
         Events are sorted oldest first.'''
 
-        return self.past_events().filter(invoice_status='not-invoiced') \
-                                 .order_by('start')
+        return self.not_cancelled().past_events() \
+                   .filter(invoice_status='not-invoiced') \
+                   .order_by('start')
 
     def metadata_changed(self):
         """Return events for which remote metatags have been updated."""


Test patch:
Repository: carpentries/amy

Issue:
Add `notes` field to TrainingRequest
So that you can, for example, add notes for non-acceptance (that is, why a TrainingRequest was discarded).

Add `notes` field to TrainingRequest
So that you can, for example, add notes for non-acceptance (that is, why a TrainingRequest was discarded).



Discussion:

Resolving patch:
diff --git a/workshops/migrations/0125_trainingrequest_notes.py b/workshops/migrations/0125_trainingrequest_notes.py
new file mode 100644
--- /dev/null
+++ b/workshops/migrations/0125_trainingrequest_notes.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.12 on 2017-01-21 21:52
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('workshops', '0124_merge'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='trainingrequest',
+            name='notes',
+            field=models.TextField(blank=True, help_text='Admin notes'),
+        ),
+    ]
diff --git a/workshops/models.py b/workshops/models.py
--- a/workshops/models.py
+++ b/workshops/models.py
@@ -2120,6 +2120,8 @@ class TrainingRequest(ActiveMixin, CreatedUpdatedMixin, models.Model):
         help_text='What else do you want us to know?',
         verbose_name='Anything else?')
 
+    notes = models.TextField(blank=True, help_text='Admin notes')
+
     def clean(self):
         super().clean()
 


Test patch:
Repository: carpentries/amy

Issue:
Optimize SQL queries on trainees list view
On production data, we make ~200 queries in 600 ms on my local machine, which is way too much.



Discussion:

Resolving patch:
diff --git a/workshops/views.py b/workshops/views.py
--- a/workshops/views.py
+++ b/workshops/views.py
@@ -2746,7 +2746,12 @@ def duplicates(request):
 def all_trainingrequests(request):
     filter = TrainingRequestFilter(
         request.GET,
-        queryset=TrainingRequest.objects.all()
+        queryset=TrainingRequest.objects.all().prefetch_related(
+            Prefetch('person__task_set',
+                     to_attr='training_tasks',
+                     queryset=Task.objects.filter(role__name='learner',
+                                                  event__tags__name='TTT')),
+        )
     )
 
     requests = get_pagination_items(request, filter)
@@ -3117,9 +3122,20 @@ class TrainingProgressDelete(RedirectSupportMixin, OnlyForAdminsMixin,
 def all_trainees(request):
     filter = TraineeFilter(
         request.GET,
-        queryset=Person.objects \
-            .annotate_with_instructor_eligibility() \
-            .defer('notes')  # notes are too large, so we defer them \
+        queryset=Person.objects
+            .annotate_with_instructor_eligibility()
+            .defer('notes')  # notes are too large, so we defer them
+            .prefetch_related(
+                Prefetch('task_set',
+                         to_attr='training_tasks',
+                         queryset=Task.objects.filter(role__name='learner',
+                                                      event__tags__name='TTT')),
+                'training_tasks__event',
+                'trainingrequest_set',
+                'trainingprogress_set',
+                'trainingprogress_set__requirement',
+                'trainingprogress_set__evaluated_by',
+            )
             .annotate(
                 is_swc_instructor=Sum(Case(When(badges__name='swc-instructor',
                                                 then=1),


Test patch:
Repository: carpentries/amy

Issue:
Filter workshops by Country
Allowing filtering of workshop by Country would be extreemley useful.
Filter workshops by Country
Allowing filtering of workshop by Country would be extreemley useful.


Discussion:

Resolving patch:
diff --git a/workshops/filters.py b/workshops/filters.py
--- a/workshops/filters.py
+++ b/workshops/filters.py
@@ -123,6 +123,8 @@ class EventFilter(AMYFilterSet):
         choices=(EMPTY_SELECTION, ) + Event.INVOICED_CHOICES,
     )
 
+    country = AllCountriesFilter()
+
     class Meta:
         model = Event
         fields = [
@@ -132,6 +134,7 @@ class Meta:
             'administrator',
             'invoice_status',
             'completed',
+            'country',
         ]
         order_by = ['-slug', 'slug', 'start', '-start', 'end', '-end']
 


Test patch:
Repository: carpentries/amy

Issue:
Export "created-on" field for training requests
It would be good to be able to export the "created-on" field when exporting training requests. This field shows up in the view [here](https://amy.software-carpentry.org/workshops/training_requests/) but isn't included in the downloaded csv when you click "Download all requests as CSV". 


Discussion:

Resolving patch:
diff --git a/workshops/views.py b/workshops/views.py
--- a/workshops/views.py
+++ b/workshops/views.py
@@ -3018,6 +3018,7 @@ def download_trainingrequests(request):
 
     writer = csv.writer(response)
     writer.writerow([
+        'Created-on',
         'State',
         'Matched Trainee',
         'Group Name',
@@ -3047,6 +3048,7 @@ def download_trainingrequests(request):
     ])
     for req in TrainingRequest.objects.all():
         writer.writerow([
+            '{:%Y-%m-%d %H:%M}'.format(req.created_at),
             req.get_state_display(),
             '—' if req.person is None else req.person.get_full_name(),
             req.group_name,


Test patch:
diff --git a/workshops/test/test_training_request.py b/workshops/test/test_training_request.py
--- a/workshops/test/test_training_request.py
+++ b/workshops/test/test_training_request.py
@@ -303,13 +303,13 @@ def setUp(self):
         self._setUpUsersAndLogin()
 
     def test_basic(self):
-        create_training_request(state='p', person=None)
+        tr = create_training_request(state='p', person=None)
         rv = self.client.get(reverse('download_trainingrequests'))
         self.assertEqual(rv.status_code, 200)
         got = rv.content.decode('utf-8')
         expected = (
-            'State,Matched Trainee,Group Name,Personal,Family,Email,GitHub username,Occupation,Occupation (other),Affiliation,Location,Country,Expertise areas,Expertise areas (other),Under-represented,Previous Involvement,Previous Training in Teaching,Previous Training (other),Previous Training (explanation),Programming Language Usage,Reason,Teaching Frequency Expectation,Teaching Frequency Expectation (other),Max Travelling Frequency,Max Travelling Frequency (other),Comment\r\n'
-            'Pending,—,,John,Smith,john@smith.com,,Other:,,AGH University of Science and Technology,Cracow,PL,,,No,,None,,,Every day,Just for fun.,Several times a year,,Once a year,,\r\n'
+            'Created-on,State,Matched Trainee,Group Name,Personal,Family,Email,GitHub username,Occupation,Occupation (other),Affiliation,Location,Country,Expertise areas,Expertise areas (other),Under-represented,Previous Involvement,Previous Training in Teaching,Previous Training (other),Previous Training (explanation),Programming Language Usage,Reason,Teaching Frequency Expectation,Teaching Frequency Expectation (other),Max Travelling Frequency,Max Travelling Frequency (other),Comment\r\n'
+            '{:%Y-%m-%d %H:%M}'.format(tr.created_at) + ',Pending,—,,John,Smith,john@smith.com,,Other:,,AGH University of Science and Technology,Cracow,PL,,,No,,None,,,Every day,Just for fun.,Several times a year,,Once a year,,\r\n'
         )
         self.assertEqual(got, expected)
Repository: carpentries/amy

Issue:
Redirect index page
https://amy.software-carpentry.org/ should redirect to https://amy.software-carpentry.org/workshops rather than returning a 404.


Discussion:

Resolving patch:
diff --git a/amy/urls.py b/amy/urls.py
--- a/amy/urls.py
+++ b/amy/urls.py
@@ -2,8 +2,10 @@
 from django.conf.urls import include, url
 from django.contrib import admin
 from django.contrib.auth import views as auth_views
+from django.views.generic import RedirectView
 
 urlpatterns = [
+    url(r'^$', RedirectView.as_view(pattern_name='dispatch')),
     url(r'^workshops/admin/', include(admin.site.urls)),
 ]
 if settings.ENABLE_PYDATA:


Test patch:
diff --git a/workshops/test/test_security.py b/workshops/test/test_security.py
--- a/workshops/test/test_security.py
+++ b/workshops/test/test_security.py
@@ -2,7 +2,7 @@
 from django.contrib.admin.sites import AdminSite
 from django.contrib.auth.models import Group
 from django.core.urlresolvers import reverse
-from django.views.generic import View
+from django.views.generic import View, RedirectView
 from rest_framework.views import APIView
 
 from amy import urls
@@ -284,12 +284,15 @@ def test_all_views_have_explicit_access_control_defined(self):
                     is_model_admin = isinstance(model_admin, ModelAdmin)
                     is_admin_site = isinstance(admin_site, AdminSite)
                     is_api_view = class_ is not None and issubclass(class_, APIView)
+                    is_redirect_view = class_ is not None and issubclass(class_, RedirectView)
                     is_view = class_ is not None and issubclass(class_, View)
 
                     if is_model_admin or is_admin_site:
                         pass  # ignore admin views
                     elif is_api_view:
                         pass  # ignore REST API views
+                    elif is_redirect_view:
+                        pass  # ignore pure redirect views
                     else:
                         assert is_view