Repository: akvo/akvo-rsr

Issue:
TypeError in 'old' organisation admin
## Test plan

GIVEN the 'old' organisation admin (`/admin/rsr/organisation/`)
WHEN trying to save a new or existing organisation
THEN it should always save successfully and not display a TypeError
## Issue description

When saving an organisation in the 'old' admin (`/admin/rsr/organisation/`), an error sometimes occurs. This should be fixed before we can release.



Discussion:

Resolving patch:
diff --git a/akvo/rsr/admin.py b/akvo/rsr/admin.py
--- a/akvo/rsr/admin.py
+++ b/akvo/rsr/admin.py
@@ -59,6 +59,7 @@ class OrganisationLocationInline(NestedStackedInline):
     model = get_model('rsr', 'organisationlocation')
     fields = ('latitude', 'longitude', 'city', 'state', 'address_1', 'address_2', 'postcode',
               'country')
+    fk_name = 'location_target'
 
     def get_extra(self, request, obj=None, **kwargs):
         if obj:
@@ -69,8 +70,8 @@ def get_extra(self, request, obj=None, **kwargs):
 
 class OrganisationTotalBudgetLineInline(NestedTabularInline):
     model = get_model('rsr', 'organisationtotalbudgetline')
-    inlines = ()
     fields = ('currency', 'value', 'value_date', 'reference', 'text')
+    fk_name = 'budget'
 
     def get_extra(self, request, obj=None, **kwargs):
         return 0
@@ -80,6 +81,7 @@ class OrganisationTotalBudgetInline(NestedTabularInline):
     model = get_model('rsr', 'organisationtotalbudget')
     inlines = (OrganisationTotalBudgetLineInline,)
     fields = ('currency', 'value', 'value_date', 'period_start', 'period_end', 'status')
+    fk_name = 'organisation'
 
     def get_extra(self, request, obj=None, **kwargs):
         if obj:
@@ -90,8 +92,8 @@ def get_extra(self, request, obj=None, **kwargs):
 
 class OrganisationRecipientOrgBudgetLineInline(NestedTabularInline):
     model = get_model('rsr', 'organisationrecipientorgbudgetline')
-    inlines = ()
     fields = ('currency', 'value', 'value_date', 'reference', 'text')
+    fk_name = 'budget'
 
     def get_extra(self, request, obj=None, **kwargs):
         return 0
@@ -113,8 +115,8 @@ def get_extra(self, request, obj=None, **kwargs):
 
 class OrganisationRegionBudgetLineInline(NestedTabularInline):
     model = get_model('rsr', 'organisationregionbudgetline')
-    inlines = ()
     fields = ('currency', 'value', 'value_date', 'reference', 'text')
+    fk_name = 'budget'
 
     def get_extra(self, request, obj=None, **kwargs):
         return 0
@@ -125,6 +127,7 @@ class OrganisationRegionBudgetInline(NestedTabularInline):
     inlines = (OrganisationRegionBudgetLineInline, )
     fields = ('region', 'region_vocabulary', 'region_vocabulary_uri', 'text', 'currency', 'value',
               'value_date', 'period_start', 'period_end', 'status')
+    fk_name = 'organisation'
 
     def get_extra(self, request, obj=None, **kwargs):
         if obj:
@@ -135,8 +138,8 @@ def get_extra(self, request, obj=None, **kwargs):
 
 class OrganisationCountryBudgetLineInline(NestedTabularInline):
     model = get_model('rsr', 'organisationcountrybudgetline')
-    inlines = ()
     fields = ('currency', 'value', 'value_date', 'reference', 'text')
+    fk_name = 'budget'
 
     def get_extra(self, request, obj=None, **kwargs):
         return 0
@@ -147,6 +150,7 @@ class OrganisationCountryBudgetInline(NestedTabularInline):
     inlines = (OrganisationCountryBudgetLineInline, )
     fields = ('country', 'text', 'currency', 'value', 'value_date', 'period_start', 'period_end',
               'status')
+    fk_name = 'organisation'
 
     def get_extra(self, request, obj=None, **kwargs):
         if obj:
@@ -157,8 +161,8 @@ def get_extra(self, request, obj=None, **kwargs):
 
 class OrganisationTotalExpenditureLineInline(NestedTabularInline):
     model = get_model('rsr', 'organisationexpenseline')
-    inlines = ()
     fields = ('currency', 'value', 'value_date', 'reference', 'text')
+    fk_name = 'expenditure'
 
     def get_extra(self, request, obj=None, **kwargs):
         return 0
@@ -168,6 +172,7 @@ class OrganisationTotalExpenditureInline(NestedTabularInline):
     model = get_model('rsr', 'organisationtotalexpenditure')
     inlines = (OrganisationTotalExpenditureLineInline, )
     fields = ('currency', 'value', 'value_date', 'period_start', 'period_end')
+    fk_name = 'organisation'
 
     def get_extra(self, request, obj=None, **kwargs):
         if obj:
@@ -178,8 +183,8 @@ def get_extra(self, request, obj=None, **kwargs):
 
 class OrganisationDocumentCategoryInline(NestedTabularInline):
     model = get_model('rsr', 'organisationdocumentcategory')
-    inlines = ()
     fields = ('category', )
+    fk_name = 'document'
 
     def get_extra(self, request, obj=None, **kwargs):
         return 0
@@ -187,8 +192,8 @@ def get_extra(self, request, obj=None, **kwargs):
 
 class OrganisationDocumentCountryInline(NestedTabularInline):
     model = get_model('rsr', 'organisationdocumentcountry')
-    inlines = ()
     fields = ('country', 'text')
+    fk_name = 'document'
 
     def get_extra(self, request, obj=None, **kwargs):
         return 0
@@ -198,6 +203,7 @@ class OrganisationDocumentInline(NestedStackedInline):
     model = get_model('rsr', 'organisationdocument')
     inlines = (OrganisationDocumentCategoryInline, OrganisationDocumentCountryInline)
     fields = ('url', 'document', 'format', 'title', 'title_language', 'language', 'document_date')
+    fk_name = 'organisation'
 
     def get_extra(self, request, obj=None, **kwargs):
         if obj:
@@ -208,8 +214,8 @@ def get_extra(self, request, obj=None, **kwargs):
 
 class OrganisationCustomFieldInline(NestedTabularInline):
     model = get_model('rsr', 'organisationcustomfield')
-    inlines = ()
     fields = ('name', 'type', 'section', 'order', 'max_characters', 'mandatory', 'help_text')
+    fk_name = 'organisation'
 
     def get_extra(self, request, obj=None, **kwargs):
         if obj:
@@ -283,6 +289,101 @@ def get_queryset(self, request):
                     org_set.add(co_org.pk)
         return Organisation.objects.filter(pk__in=org_set).distinct()
 
+    @csrf_protect_m
+    @transaction.atomic
+    def add_view(self, request, form_url='', extra_context=None):
+        "The 'add' admin view for this model."
+        model = self.model
+        opts = model._meta
+
+        ModelForm = self.get_form(request)
+        formsets = []
+        inline_instances = self.get_inline_instances(request, None)
+        if request.method == 'POST':
+            form = ModelForm(request.POST, request.FILES)
+            if form.is_valid():
+                new_object = self.save_form(request, form, change=False)
+                form_validated = True
+            else:
+                form_validated = False
+                new_object = self.model()
+            prefixes = {}
+            for FormSet, inline in zip(self.get_formsets(request), inline_instances):
+                FormSet = FormSet[0]
+                prefix = FormSet.get_default_prefix()
+                # check if we're trying to create a new project by copying an existing one. If so
+                # we ignore location and benchmark inlines
+                if "_saveasnew" not in request.POST or not prefix in ['benchmarks',
+                                                                      'rsr-location-content_type-object_id']:
+                    # end of add although the following block is indented as a result
+                    prefixes[prefix] = prefixes.get(prefix, 0) + 1
+                    if prefixes[prefix] != 1 or not prefix:
+                        prefix = "%s-%s" % (prefix, prefixes[prefix])
+                    formset = FormSet(data=request.POST, files=request.FILES,
+                                      instance=new_object,
+                                      save_as_new="_saveasnew" in request.POST,
+                                      prefix=prefix, queryset=inline.get_queryset(request))
+                    formsets.append(formset)
+            if all_valid(formsets) and form_validated:
+                self.save_model(request, new_object, form, False)
+                self.save_related(request, form, formsets, False)
+                self.log_addition(request, new_object)
+                return self.response_add(request, new_object)
+        else:
+            # Prepare the dict of initial data from the request.
+            # We have to special-case M2Ms as a list of comma-separated PKs.
+            initial = dict(request.GET.items())
+            for k in initial:
+                try:
+                    f = opts.get_field(k)
+                except models.FieldDoesNotExist:
+                    continue
+                if isinstance(f, models.ManyToManyField):
+                    initial[k] = initial[k].split(",")
+            form = ModelForm(initial=initial)
+            prefixes = {}
+            for FormSet, inline in zip(self.get_formsets(request), inline_instances):
+                FormSet = FormSet[0]
+                prefix = FormSet.get_default_prefix()
+                prefixes[prefix] = prefixes.get(prefix, 0) + 1
+                if prefixes[prefix] != 1 or not prefix:
+                    prefix = "%s-%s" % (prefix, prefixes[prefix])
+
+                formset = FormSet(instance=self.model(), prefix=prefix,
+                                  queryset=inline.get_queryset(request))
+                formsets.append(formset)
+
+        adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)),
+                                      self.get_prepopulated_fields(request),
+                                      self.get_readonly_fields(request),
+                                      model_admin=self)
+        media = self.media + adminForm.media
+
+        inline_admin_formsets = []
+        for inline, formset in zip(inline_instances, formsets):
+            fieldsets = list(inline.get_fieldsets(request))
+            readonly = list(inline.get_readonly_fields(request))
+            prepopulated = dict(inline.get_prepopulated_fields(request))
+            inline_admin_formset = helpers.InlineAdminFormSet(
+                inline, formset, fieldsets, prepopulated, readonly, model_admin=self
+            )
+            inline_admin_formsets.append(inline_admin_formset)
+            media = media + inline_admin_formset.media
+
+        context = {
+            'title': _('Add %s') % force_text(opts.verbose_name),
+            'adminform': adminForm,
+            'is_popup': IS_POPUP_VAR in request.REQUEST,
+            'show_delete': False,
+            'media': media,
+            'inline_admin_formsets': inline_admin_formsets,
+            'errors': helpers.AdminErrorList(form, formsets),
+            'app_label': opts.app_label,
+            'preserved_filters': self.get_preserved_filters(request),
+        }
+        context.update(extra_context or {})
+        return self.render_change_form(request, context, form_url=form_url, add=True)
+
 admin.site.register(get_model('rsr', 'organisation'), OrganisationAdmin)
 
 
@@ -916,6 +1017,7 @@ def add_view(self, request, form_url='', extra_context=None):
                 new_object = self.model()
             prefixes = {}
             for FormSet, inline in zip(self.get_formsets(request), inline_instances):
+                FormSet = FormSet[0]
                 prefix = FormSet.get_default_prefix()
                 # check if we're trying to create a new project by copying an existing one. If so
                 # we ignore location and benchmark inlines
@@ -948,6 +1050,7 @@ def add_view(self, request, form_url='', extra_context=None):
             form = ModelForm(initial=initial)
             prefixes = {}
             for FormSet, inline in zip(self.get_formsets(request), inline_instances):
+                FormSet = FormSet[0]
                 prefix = FormSet.get_default_prefix()
                 prefixes[prefix] = prefixes.get(prefix, 0) + 1
                 if prefixes[prefix] != 1 or not prefix:
diff --git a/akvo/rsr/models/organisation_document.py b/akvo/rsr/models/organisation_document.py
--- a/akvo/rsr/models/organisation_document.py
+++ b/akvo/rsr/models/organisation_document.py
@@ -133,8 +133,8 @@ class OrganisationDocumentCountry(models.Model):
 
     class Meta:
         app_label = 'rsr'
-        verbose_name = _(u'document category')
-        verbose_name_plural = _(u'document categories')
+        verbose_name = _(u'document country')
+        verbose_name_plural = _(u'document countries')
         ordering = ['-id', ]
 
     def __unicode__(self):


Test patch: