Repository: City-of-Helsinki/linkedevents

Issue:
Allow editing events from other organization data sources by organization users through API



Discussion:

Resolving patch:
diff --git a/events/admin.py b/events/admin.py
--- a/events/admin.py
+++ b/events/admin.py
@@ -84,7 +84,7 @@ def nr_org_admins(self, obj):
 
 
 class DataSourceAdmin(BaseAdmin):
-    fields = ('id', 'name', 'api_key', 'owner')
+    fields = ('id', 'name', 'api_key', 'owner', 'user_editable')
 
 admin.site.register(DataSource, DataSourceAdmin)
 
diff --git a/events/migrations/0039_add_user_editable_to_datasource.py b/events/migrations/0039_add_user_editable_to_datasource.py
new file mode 100644
--- /dev/null
+++ b/events/migrations/0039_add_user_editable_to_datasource.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.9.13 on 2017-06-15 11:50
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+from django.conf import settings
+
+
+def forward(apps, schema_editor):
+    DataSource = apps.get_model('events', 'DataSource')
+    try:
+        system_data_source = DataSource.objects.get(id=settings.SYSTEM_DATA_SOURCE_ID)
+        system_data_source.user_editable = True
+        system_data_source.save()
+    except DataSource.DoesNotExist:
+        pass
+
+
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('events', '0038_add_index_for_last_modified_time'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='datasource',
+            name='user_editable',
+            field=models.BooleanField(default=False, verbose_name='Objects may be edited by users'),
+        ),
+        migrations.AlterField(
+            model_name='datasource',
+            name='owner',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='owned_systems', to='events.Organization'),
+        ),
+        migrations.RunPython(forward, migrations.RunPython.noop)
+    ]
diff --git a/events/models.py b/events/models.py
--- a/events/models.py
+++ b/events/models.py
@@ -62,7 +62,8 @@ class DataSource(models.Model):
     id = models.CharField(max_length=100, primary_key=True)
     name = models.CharField(verbose_name=_('Name'), max_length=255)
     api_key = models.CharField(max_length=128, blank=True, default='')
-    owner = models.ForeignKey('Organization', related_name='owned_system', null=True, blank=True)
+    owner = models.ForeignKey('Organization', related_name='owned_systems', null=True, blank=True)
+    user_editable = models.BooleanField(default=False, verbose_name=_('Objects may be edited by users'))
 
     def __str__(self):
         return self.id
@@ -134,7 +135,7 @@ def save(self, *args, **kwargs):
         super(Image, self).save(*args, **kwargs)
 
     def is_user_editable(self):
-        return self.data_source_id == settings.SYSTEM_DATA_SOURCE_ID
+        return self.data_source.user_editable
 
     def is_admin(self, user):
         if user.is_superuser:
@@ -480,8 +481,8 @@ def __str__(self):
         return u" ".join(val)
 
     def is_user_editable(self):
-        return self.data_source_id == settings.SYSTEM_DATA_SOURCE_ID
-
+        return self.data_source.user_editable
+ 
     def is_admin(self, user):
         if user.is_superuser:
             return True


Test patch:
diff --git a/events/tests/conftest.py b/events/tests/conftest.py
--- a/events/tests/conftest.py
+++ b/events/tests/conftest.py
@@ -46,7 +46,8 @@ def api_client():
 def data_source():
     return DataSource.objects.create(
         id=settings.SYSTEM_DATA_SOURCE_ID,
-        api_key="test_api_key"
+        api_key="test_api_key",
+        user_editable=True
     )
 
 
@@ -186,10 +187,10 @@ def place(data_source, organization, administrative_division):
 
 @pytest.mark.django_db
 @pytest.fixture
-def event(place, user):
+def event(data_source, organization, place, user):
     return Event.objects.create(
-        id='test_event', location=place,
-        data_source=place.data_source, publisher=place.publisher,
+        id=data_source.id + ':test_event', location=place,
+        data_source=data_source, publisher=organization,
         last_modified_by=user,
         start_time=timezone.now(),
         end_time=timezone.now() + timedelta(hours=1),
@@ -219,10 +220,10 @@ def place3(data_source, organization):
 
 @pytest.mark.django_db
 @pytest.fixture
-def event2(place2, user2):
+def event2(other_data_source, organization2, place2, user2):
     return Event.objects.create(
         id='test_event 2', location=place2,
-        data_source=place2.data_source, publisher=place2.publisher,
+        data_source=other_data_source, publisher=organization2,
         last_modified_by=user2,
         start_time=timezone.now(),
         end_time=timezone.now() + timedelta(hours=1),
diff --git a/events/tests/test_event_images_v1.py b/events/tests/test_event_images_v1.py
--- a/events/tests/test_event_images_v1.py
+++ b/events/tests/test_event_images_v1.py
@@ -235,15 +235,42 @@ def test__image_cannot_be_edited_outside_organization(api_client, settings, list
     organization2.admin_users.add(user2)
     api_client.force_authenticate(user2)
     detail_url = reverse('image-detail', kwargs={'pk': response.data['id']})
-    new_image_data = response.data
-    new_image_data['url'] = 'https://commons.wikimedia.org/wiki/File:Common_Squirrel.jpg'
-    new_image_data['publisher'] = 'test_organization2'
-    response2 = api_client.put(detail_url, response.data)
+    response2 = api_client.put(detail_url, {'name': 'this is needed'})
     assert response2.status_code == 403
     response3 = api_client.delete(detail_url)
     assert response3.status_code == 403
 
 
+@pytest.mark.django_db
+def test__image_from_another_data_source_can_be_edited_by_admin(api_client, list_url, image_data, data_source, user,
+                                                                organization, other_data_source):
+    organization.admin_users.add(user)
+    api_client.force_authenticate(user)
+
+    response = api_client.post(list_url, image_data)
+    assert response.status_code == 201
+    assert Image.objects.all().count() == 1
+
+    image = Image.objects.get(pk=response.data['id'])
+    assert image.created_by == user
+    assert image.last_modified_by == user
+    assert image.publisher == organization
+    assert image.data_source == data_source
+    image.data_source = other_data_source
+    image.save()
+    other_data_source.user_editable = True
+    other_data_source.owner = organization
+    other_data_source.save()
+    assert image.data_source == other_data_source
+    assert other_data_source in organization.owned_systems.all()
+
+    # user can still edit or delete the image
+    detail_url = reverse('image-detail', kwargs={'pk': response.data['id']})
+    response2 = api_client.put(detail_url, {'name': 'this is needed'})
+    assert response2.status_code == 200
+    response3 = api_client.delete(detail_url)
+    assert response3.status_code == 204
+
 @pytest.mark.django_db
 def test__image_cannot_be_edited_outside_organization_with_apikey(api_client, settings, list_url, image_data, user, organization, organization2, other_data_source):
     organization.admin_users.add(user)
@@ -265,11 +292,7 @@ def test__image_cannot_be_edited_outside_organization_with_apikey(api_client, se
     api_client.credentials(apikey=other_data_source.api_key)
 
     detail_url = reverse('image-detail', kwargs={'pk': response.data['id']})
-    new_image_data = response.data
-    new_image_data['url'] = 'https://commons.wikimedia.org/wiki/File:Common_Squirrel.jpg'
-    new_image_data['data_source'] = 'testotherdatasourceid'
-    new_image_data['publisher'] = 'test_organization2'
-    response2 = api_client.put(detail_url, response.data)
+    response2 = api_client.put(detail_url, {'name': 'this is needed'})
     assert response2.status_code == 403
     response3 = api_client.delete(detail_url)
     assert response3.status_code == 403
diff --git a/events/tests/test_event_put.py b/events/tests/test_event_put.py
--- a/events/tests/test_event_put.py
+++ b/events/tests/test_event_put.py
@@ -413,6 +413,28 @@ def test__a_non_admin_cannot_update_an_event(api_client, event, complex_event_di
     assert response.status_code == 403
 
 
+@pytest.mark.django_db
+def test__an_admin_can_update_an_event_from_another_data_source(api_client, event2, complex_event_dict,
+                                                                other_data_source, organization, user):
+    other_data_source.owner = organization
+    other_data_source.user_editable = True
+    other_data_source.save()
+    event2.publisher = organization
+    event2.save()
+    api_client.force_authenticate(user)
+    print(event2.is_admin(user))
+    print(other_data_source.user_editable)
+    print(event2.publisher)
+    print(user.get_default_organization())
+
+    detail_url = reverse('event-detail', kwargs={'pk': event2.pk})
+    # response = api_client.get(detail_url, format='json')
+    # assert response.status_code == 200
+    response = update_with_put(api_client, detail_url, complex_event_dict)
+    print(response.data)
+    assert response.status_code == 200
+
+
 @pytest.mark.django_db
 def test__correct_api_key_can_update_an_event(api_client, event, complex_event_dict, data_source, organization):