internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: akvo/akvo-rsr

Issue:
Investigate creation of duplicate user accounts with differently cased emails
- [ ] Verify that lookups using email are using `__iexact` or something like that. 
- [ ] Figure out a plan for existing duplicates


Discussion:

Resolving patch:
diff --git a/akvo/rest/views/utils.py b/akvo/rest/views/utils.py
--- a/akvo/rest/views/utils.py
+++ b/akvo/rest/views/utils.py
@@ -63,7 +63,7 @@ def create_invited_user(email):
     User = get_user_model()
     # Check if the user already exists, based on the email address
     try:
-        invited_user = User.objects.get(email=email)
+        invited_user = User.objects.get(email__iexact=email)
     except User.DoesNotExist:
         try:
             invited_user = User.objects.create_user(username=email, email=email)


Test patch:
diff --git a/akvo/rsr/tests/rest/test_user_management.py b/akvo/rsr/tests/rest/test_user_management.py
--- a/akvo/rsr/tests/rest/test_user_management.py
+++ b/akvo/rsr/tests/rest/test_user_management.py
@@ -12,7 +12,7 @@
 from django.contrib.auth.models import Group
 from mock import patch
 
-from akvo.rsr.models import Employment
+from akvo.rsr.models import Employment, User
 from akvo.rest.views import user_management
 from akvo.rsr import signals
 from akvo.rsr.tests.base import BaseTestCase
@@ -146,6 +146,35 @@ def test_should_approve_existing_employment_for_active_user(self, invite, mock_s
         employment = Employment.objects.get(user__email=email, organisation_id=self.org.id)
         self.assertTrue(employment.is_approved)
 
+    @patch.object(signals, 'rsr_send_mail')
+    @patch.object(user_management, 'send_user_invitation')
+    def test_lower_case_user_not_created(self, invite, mock_send):
+        # Given
+        group = Group.objects.get(name='Users')
+        email = 'RSR-tests-email@akvo.org'
+        user = self.create_user(email=email, password=self.password, is_active=True)
+
+        user_data = json.dumps({
+            'organisation': self.org.id,
+            'group': group.id,
+            'email': email,
+        })
+        data = {'user_data': user_data}
+
+        # When
+        response = self.c.post(
+            '/rest/v1/invite_user/?format=json',
+            json.dumps(data),
+            content_type='application/json',
+        )
+
+        # Then
+        self.assertEqual(response.status_code, 201)
+        self.assertFalse(User.objects.filter(email=email.lower()).exists())
+        self.assertEqual(user.approved_employments().count(), 1)
+        mock_send.assert_called_once()
+        invite.assert_not_called()
+
     def test_should_approve_new_employment(self):
         # Given
         user = self.create_user('foo@bar.com', self.password)
Repository: akvo/akvo-rsr

Issue:
Allow changing reporting organisation using Project.set_reporting_org



Discussion:

Resolving patch:
diff --git a/akvo/rsr/eutf_signals.py b/akvo/rsr/eutf_signals.py
--- a/akvo/rsr/eutf_signals.py
+++ b/akvo/rsr/eutf_signals.py
@@ -54,8 +54,7 @@ def set_eutf_as_reporting_organisation(project):
     )
     if not created and partnership.organisation != eutf:
         old_reporting_organisation = partnership.organisation
-        partnership.organisation = eutf
-        partnership.save(update_fields=['organisation'])
+        project.set_reporting_org(eutf)
         Partnership.objects.create(
             project=project,
             organisation=old_reporting_organisation,
diff --git a/akvo/rsr/models/project.py b/akvo/rsr/models/project.py
--- a/akvo/rsr/models/project.py
+++ b/akvo/rsr/models/project.py
@@ -670,13 +670,11 @@ def publishing_orgs(self):
         return self.partners.filter(can_create_projects=True)
 
     def set_reporting_org(self, organisation):
-        """ Set the reporting-org for the project.
-            Currently protests if you try to set another organisation when one is already set.
-        """
-        if self.reporting_org is not None:
-            # TODO: should we allow overwriting the existing reporting-org here?
-            if self.reporting_org != organisation:
-                raise MultipleReportingOrgs
+        """ Set the reporting-org for the project."""
+        if self.reporting_partner is not None:
+            partnership = self.reporting_partner
+            partnership.organisation = organisation
+            partnership.save(update_fields=['organisation'])
         else:
             Partnership.objects.create(
                 project=self,


Test patch:
diff --git a/akvo/rsr/tests/models/test_project.py b/akvo/rsr/tests/models/test_project.py
--- a/akvo/rsr/tests/models/test_project.py
+++ b/akvo/rsr/tests/models/test_project.py
@@ -153,6 +153,16 @@ def test_no_reporting_org_codelist(self):
 
         self.assertIsNone(codelist)
 
+    def test_set_reporting_org_overrides_reporting_partner(self):
+        project = self.create_project('Project')
+        org1 = self.create_organisation('Org1')
+        project.set_reporting_org(org1)
+
+        org2 = self.create_organisation('Org2')
+        project.set_reporting_org(org2)
+
+        self.assertEqual(org2, project.reporting_org)
+
     def test_use_project_roles_changes_when_reporting_partner_uses_it(self):
         project = self.create_project('Project')
         org = self.create_organisation('Organisation')
Repository: akvo/akvo-rsr

Issue:
Add baseline year & value to /project/:id/indicator/:id/



Discussion:

Resolving patch:
diff --git a/akvo/rest/views/project_overview.py b/akvo/rest/views/project_overview.py
--- a/akvo/rest/views/project_overview.py
+++ b/akvo/rest/views/project_overview.py
@@ -70,6 +70,8 @@ def project_result_overview(request, project_pk, result_pk):
                 'description': i.description,
                 'period_count': len(i.periods.all()),
                 'type': 'quantitative' if i.type == QUANTITATIVE else 'qualitative',
+                'baseline_year': i.baseline_year,
+                'baseline_value': i.baseline_value,
                 'measure': (
                     'unit' if i.measure == '1' else 'percentage' if i.measure == '2' else None),
                 'periods': _drilldown_indicator_periods_contributions(i, aggregate_targets)
@@ -95,6 +97,8 @@ def project_indicator_overview(request, project_pk, indicator_pk):
         'description': indicator.description,
         'period_count': len(indicator.periods.all()),
         'type': 'quantitative' if indicator.type == QUANTITATIVE else 'qualitative',
+        'baseline_year': indicator.baseline_year,
+        'baseline_value': indicator.baseline_value,
         'measure': (
             'unit' if indicator.measure == '1' else 'percentage' if indicator.measure == '2' else None),
         'periods': _drilldown_indicator_periods_contributions(indicator)


Test patch:
Repository: akvo/akvo-rsr

Issue:
Add logging for permission changes in FAC
Project editor logging doesn't log these changes since this view doesn't use the RSR Base view. We could add logging separately using the util function, for this view. 


Discussion:

Resolving patch:
diff --git a/akvo/rest/views/project_role.py b/akvo/rest/views/project_role.py
--- a/akvo/rest/views/project_role.py
+++ b/akvo/rest/views/project_role.py
@@ -23,7 +23,7 @@
     ProjectRoleSerializer,
 )
 from akvo.rest.views.utils import create_invited_user
-from akvo.utils import send_user_invitation
+from akvo.utils import send_user_invitation, log_project_changes
 
 
 Role = namedtuple("Role", ("email", "role"))
@@ -93,11 +93,15 @@ def project_roles(request, project_pk):
 
             # Delete roles
             for project_role in existing_roles - new_roles:
-                ProjectRole.objects.filter(
+                deleted_roles = ProjectRole.objects.filter(
                     project=project,
                     user__email=project_role.email,
                     group__name=project_role.role,
-                ).delete()
+                )
+                deleted_role = deleted_roles.first()
+                deleted_roles.delete()
+                if deleted_role:
+                    log_project_changes(request.user, project, deleted_role, {}, 'deleted')
 
             # Create roles
             created = [
@@ -109,6 +113,8 @@ def project_roles(request, project_pk):
                 for project_role in (new_roles - existing_roles)
             ]
             ProjectRole.objects.bulk_create(created)
+            for role in created:
+                log_project_changes(request.user, project, role, {}, 'added')
 
     if not project.use_project_roles:
         roles = []


Test patch:
Repository: akvo/akvo-rsr

Issue:
Show project's locations in EUTF Syria custom project report.



Discussion:

Resolving patch:
diff --git a/akvo/rsr/views/py_reports/results_indicators_with_map_pdf_reports.py b/akvo/rsr/views/py_reports/results_indicators_with_map_pdf_reports.py
--- a/akvo/rsr/views/py_reports/results_indicators_with_map_pdf_reports.py
+++ b/akvo/rsr/views/py_reports/results_indicators_with_map_pdf_reports.py
@@ -109,6 +109,7 @@ def _render_project_report(request, project_id, with_map=False, with_disaggregat
 
     project = get_object_or_404(
         Project.objects.prefetch_related(
+            'locations',
             'partners',
             'related_projects',
             'related_to_projects',
@@ -120,14 +121,10 @@ def _render_project_report(request, project_id, with_map=False, with_disaggregat
         pk=project_id
     )
     project_location = project.primary_location
-    locations = [project_location]
-    if project.parents().count():
-        locations.append(project.parents().first().primary_location)
-    if project.children().count():
-        for child in project.children_all().published():
-            locations.append(child.primary_location)
+    coordinates = None
 
     if with_map:
+        locations = project.locations.all()
         coordinates = [Coordinate(loc.latitude, loc.longitude) for loc in locations if loc]
 
     now = datetime.today()


Test patch:
Repository: akvo/akvo-rsr

Issue:
Redirect everyone to the new project directory
- [ ] Test plan | Unit test | Integration test
- [ ] Documentation



Discussion:

Resolving patch:
diff --git a/akvo/rest/views/project.py b/akvo/rest/views/project.py
--- a/akvo/rest/views/project.py
+++ b/akvo/rest/views/project.py
@@ -340,8 +340,8 @@ def project_location_geojson(request):
 def add_project_to_program(request, program_pk):
     program = get_object_or_404(Project, pk=program_pk)
     parent_pk = request.data.get('parent')
-    if parent_pk is None:
-        parent_pk = program_pk
+    if parent_pk is not None:
+        program = get_object_or_404(Project, pk=parent_pk)
 
     project = Project.objects.create()
     Project.new_project_created(project.id, request.user)   # Log creation


Test patch:
diff --git a/akvo/rsr/tests/rest/test_project.py b/akvo/rsr/tests/rest/test_project.py
--- a/akvo/rsr/tests/rest/test_project.py
+++ b/akvo/rsr/tests/rest/test_project.py
@@ -403,6 +403,35 @@ def test_add_project_to_program(self):
         partnership = child_project.partnerships.get(organisation=org2)
         self.assertIsNotNone(partnership.iati_organisation_role, Partnership.IATI_ACCOUNTABLE_PARTNER)
 
+    def test_add_project_to_sub_program(self):
+        org = self.create_organisation('Organisation')
+        program = self.create_project('Program')
+        self.make_partner(program, org, Partnership.IATI_REPORTING_ORGANISATION)
+        self.create_project_hierarchy(org, program, 2)
+        Result.objects.create(project=program)
+        for validation_set in ProjectEditorValidationSet.objects.all():
+            program.add_validation_set(validation_set)
+        org2 = self.create_organisation('Delegation')
+        self.make_employment(self.user, org2, 'Admins')
+        sub_program = self.create_project('Sub-Program')
+        sub_program.add_to_program(program)
+        # Add an extra result at the sub program level
+        Result.objects.create(project=sub_program)
+
+        data = {
+            'parent': sub_program.pk
+        }
+        response = self.c.post('/rest/v1/program/{}/add-project/?format=json'.format(program.id), data=data)
+
+        self.assertEqual(response.status_code, 201)
+        child_project = Project.objects.get(id=response.data['id'])
+        self.assertEqual(child_project.reporting_org, program.reporting_org)
+        self.assertEqual(child_project.parents_all().first(), sub_program)
+        self.assertEqual(child_project.results.count(), sub_program.results.count())
+        self.assertEqual(child_project.validations.count(), program.validations.count())
+        partnership = child_project.partnerships.get(organisation=org2)
+        self.assertIsNotNone(partnership.iati_organisation_role, Partnership.IATI_ACCOUNTABLE_PARTNER)
+
 
 class TargetsAtAtributeTestCase(BaseTestCase):
Repository: akvo/akvo-rsr

Issue:
Indicator target and disaggregation targets bugs



Discussion:

Resolving patch:
diff --git a/akvo/rest/serializers/indicator.py b/akvo/rest/serializers/indicator.py
--- a/akvo/rest/serializers/indicator.py
+++ b/akvo/rest/serializers/indicator.py
@@ -37,8 +37,8 @@ class Meta:
         read_only_fields = ('id', 'indicator')
 
     def to_internal_value(self, data):
-        if 'value' in data:
-            data['value'] = str(data['value']).replace(',', '.')
+        value = data.get('value', None)
+        data['value'] = str(value).replace(',', '.') if value is not None else None
         return super().to_internal_value(data)
 
 
@@ -112,8 +112,10 @@ def validate_disaggregation_targets(self, data):
         return data
 
     def to_internal_value(self, data):
-        if 'target_value' in data:
+        if 'target_value' in data and data['target_value'] is not None:
             data['target_value'] = str(data['target_value']).replace(',', '.')
+        if 'disaggregation_targets' in data:
+            data['disaggregation_targets'] = [dt for dt in data['disaggregation_targets'] if dt]
         return super().to_internal_value(data)
 
 


Test patch:
Repository: akvo/akvo-rsr

Issue:
kickstart report markdown to docx bug



Discussion:

Resolving patch:
diff --git a/akvo/rsr/views/py_reports/docx_utils.py b/akvo/rsr/views/py_reports/docx_utils.py
--- a/akvo/rsr/views/py_reports/docx_utils.py
+++ b/akvo/rsr/views/py_reports/docx_utils.py
@@ -187,6 +187,30 @@ def _append_paragraph(self, text, element, container):
         return container
 
 
+class HeadingTagHandler(object):
+    """
+    <h1>...<h6> creates heading element inside a docx container element.
+    """
+    def __init__(self, level):
+        self.level = level
+
+    def handle_text(self, container, element):
+        paragraph = get_new_paragraph(container)
+        return self._append_heading(element.text, element, paragraph)
+
+    def handle_tail(self, container, element):
+        paragraph = get_current_paragraph(container)
+        return self._append_heading(element.text, element, paragraph)
+
+    def _append_heading(self, text, element, container):
+        container.style = 'Heading {}'.format(self.level)
+        text = trim_whitespaces(text)
+        if not text:
+            return container
+        container.add_run(text=text)
+        return container
+
+
 class StrongTagHandler(object):
     """
     <strong> Creates a bold text run inside the paragraph container.
@@ -284,20 +308,19 @@ def _append_run(self, text, container):
         return container
 
 
-strong_handler = StrongTagHandler()
 default_handler = DivTagHandler()
 _tag_handler_map = dict(
     p=ParagraphTagHandler(),
     em=EmphasisTagHandler(),
     br=LineBreakTagHandler(),
     li=ListItemTagHandler(),
-    strong=strong_handler,
-    h1=strong_handler,
-    h2=strong_handler,
-    h3=strong_handler,
-    h4=strong_handler,
-    h5=strong_handler,
-    h6=strong_handler,
+    strong=StrongTagHandler(),
+    h1=HeadingTagHandler(2),
+    h2=HeadingTagHandler(3),
+    h3=HeadingTagHandler(4),
+    h4=HeadingTagHandler(5),
+    h5=HeadingTagHandler(6),
+    h6=HeadingTagHandler(7),
 )
 
 


Test patch:
Repository: akvo/akvo-rsr

Issue:
Baseline and target score



Discussion:

Resolving patch:
diff --git a/akvo/rsr/migrations/0210_auto_20210426_0648.py b/akvo/rsr/migrations/0210_auto_20210426_0648.py
new file mode 100644
--- /dev/null
+++ b/akvo/rsr/migrations/0210_auto_20210426_0648.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.29 on 2021-04-26 06:48
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('rsr', '0209_partnersite_program'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='indicator',
+            name='baseline_score',
+            field=models.SmallIntegerField(blank=True, null=True, verbose_name='baseline score'),
+        ),
+        migrations.AddField(
+            model_name='indicator',
+            name='target_score',
+            field=models.SmallIntegerField(blank=True, null=True, verbose_name='target score'),
+        ),
+    ]
diff --git a/akvo/rsr/models/result/indicator.py b/akvo/rsr/models/result/indicator.py
--- a/akvo/rsr/models/result/indicator.py
+++ b/akvo/rsr/models/result/indicator.py
@@ -94,6 +94,8 @@ class Indicator(models.Model):
     )
     dimension_names = models.ManyToManyField('IndicatorDimensionName', related_name='indicators')
     scores = ArrayField(models.CharField(max_length=1000), default=[])
+    baseline_score = models.SmallIntegerField(_('baseline score'), null=True, blank=True)
+    target_score = models.SmallIntegerField(_('target score'), null=True, blank=True)
     enumerators = models.ManyToManyField('User', related_name='assigned_indicators')
 
     def __str__(self):


Test patch:
Repository: akvo/akvo-rsr

Issue:
Show internal notes on results pending for approval view



Discussion:

Resolving patch:
diff --git a/akvo/rest/serializers/indicator_period.py b/akvo/rest/serializers/indicator_period.py
--- a/akvo/rest/serializers/indicator_period.py
+++ b/akvo/rest/serializers/indicator_period.py
@@ -161,7 +161,7 @@ def get_updates(self, obj):
         user = self.context['request'].user
         updates = IndicatorPeriodData.objects.filter(period=obj)\
             .select_related('user')\
-            .prefetch_related('disaggregations')
+            .prefetch_related('disaggregations', 'comments', 'comments__user')
         project_id = obj.indicator.result.project_id
         viewable_updates = user.viewable_indicator_updates(project_id) if not user.is_anonymous() else []
         updates = updates.filter(pk__in=viewable_updates).distinct()
diff --git a/akvo/rest/serializers/indicator_period_data.py b/akvo/rest/serializers/indicator_period_data.py
--- a/akvo/rest/serializers/indicator_period_data.py
+++ b/akvo/rest/serializers/indicator_period_data.py
@@ -62,6 +62,7 @@ class IndicatorPeriodDataLiteSerializer(BaseRSRSerializer):
     value = serializers.SerializerMethodField()
     file_set = IndicatorPeriodDataFileSerializer(many=True, read_only=True, source='indicatorperioddatafile_set')
     photo_set = IndicatorPeriodDataPhotoSerializer(many=True, read_only=True, source='indicatorperioddataphoto_set')
+    comments = IndicatorPeriodDataCommentSerializer(read_only=True, many=True, required=False)
 
     def get_value(self, obj):
         return ensure_decimal(obj.value)
@@ -71,7 +72,7 @@ class Meta:
         fields = (
             'id', 'user_details', 'status', 'status_display', 'update_method', 'value', 'numerator', 'denominator', 'text',
             'disaggregations', 'narrative', 'photo_url', 'file_url', 'period_actual_value', 'created_at', 'last_modified_at',
-            'file_set', 'photo_set',
+            'file_set', 'photo_set', 'review_note', 'comments',
         )
 
 


Test patch:
Repository: akvo/akvo-rsr

Issue:
Intentionally click the Publish button
## Summary

When I try to open an unpublished project with Anthony, I intentionally click the Publish button while the page is still loading and button is active. After that I get an Error.
When the loading is completed, the publish button changes as inactive and cannot be clicked and then the project still unpublished.

![Screen Shot 2021-06-24 at 13 45 43](https://user-images.githubusercontent.com/20871229/123230369-63bdd180-d501-11eb-9a87-3bd6feaa7764.png)



Discussion:

Resolving patch:
diff --git a/akvo/rest/serializers/project.py b/akvo/rest/serializers/project.py
--- a/akvo/rest/serializers/project.py
+++ b/akvo/rest/serializers/project.py
@@ -290,6 +290,9 @@ def get_locations(self, obj):
 
     def get_parent(self, obj):
         p = obj.parents_all().first()
+        user = self.context['request'].user
+        if not user.can_view_project(p):
+            return None
         return (
             {'id': p.id, 'title': p.title, 'is_lead': p.is_hierarchy_root()}
             if p is not None
diff --git a/akvo/rest/views/project.py b/akvo/rest/views/project.py
--- a/akvo/rest/views/project.py
+++ b/akvo/rest/views/project.py
@@ -358,6 +358,8 @@ def project_title(request, project_pk):
     project = get_object_or_404(Project, pk=project_pk)
     hierarchy_name = project.uses_single_indicator_period()
     needs_reporting_timeout_days, _, _ = single_period_dates(hierarchy_name) if hierarchy_name else (None, None, None)
+    can_edit_project = request.user.can_edit_project(project)
+    view = 'm&e' if request.user.has_perm('rsr.do_me_manager_actions', project) else 'enumerator'
 
     data = {
         'title': project.title,
@@ -368,5 +370,7 @@ def project_title(request, project_pk):
             status=IndicatorPeriodData.STATUS_PENDING_CODE
         ).count(),
         'needs_reporting_timeout_days': needs_reporting_timeout_days,
+        'can_edit_project': can_edit_project,
+        'view': view,
     }
     return Response(data)


Test patch:
Repository: akvo/akvo-rsr

Issue:
Project updates issues



Discussion:

Resolving patch:
diff --git a/akvo/rest/fields.py b/akvo/rest/fields.py
--- a/akvo/rest/fields.py
+++ b/akvo/rest/fields.py
@@ -17,6 +17,7 @@
 from rest_framework import serializers
 from rest_framework.fields import ImageField
 from sorl.thumbnail.parsers import ThumbnailParseError
+from PIL import Image
 
 from akvo.utils import get_thumbnail
 
@@ -71,6 +72,10 @@ def to_internal_value(self, base64_data):
         else:
             data = base64_data
             file_extension = self.get_file_extension(data.name, data.read())
+            if file_extension is None:
+                data.seek(0)
+                im = Image.open(data)
+                file_extension = im.format.lower()
             self.check_file_extension(file_extension)
             data.seek(0)
 
diff --git a/akvo/rest/serializers/project_update.py b/akvo/rest/serializers/project_update.py
--- a/akvo/rest/serializers/project_update.py
+++ b/akvo/rest/serializers/project_update.py
@@ -39,7 +39,7 @@ class ProjectUpdateSerializer(BaseRSRSerializer):
     latitude = serializers.FloatField(required=False, source='primary_location.latitude')
     longitude = serializers.FloatField(required=False, source='primary_location.longitude')
     photo = Base64ImageField(required=False, allow_empty_file=True, allow_null=True)
-    video = serializers.URLField(required=False, allow_null=True)
+    video = serializers.URLField(required=False, allow_blank=True,)
     editable = serializers.SerializerMethodField()
     deletable = serializers.SerializerMethodField()
     edited = serializers.ReadOnlyField()
diff --git a/akvo/rsr/templatetags/rsr_utils.py b/akvo/rsr/templatetags/rsr_utils.py
--- a/akvo/rsr/templatetags/rsr_utils.py
+++ b/akvo/rsr/templatetags/rsr_utils.py
@@ -26,7 +26,7 @@ def img(context, obj, width, height, alt):
     if isinstance(obj, Project):
         img = obj.current_image
     elif isinstance(obj, ProjectUpdate):
-        img = obj.photos.first() if obj.photos.count() else obj.photo
+        img = obj.photos.first().photo if obj.photos.count() else obj.photo
     elif isinstance(obj, ProjectUpdatePhoto):
         img = obj.photo
     elif isinstance(obj, Organisation):


Test patch: