specify / specify7

Specify 7
https://www.specifysoftware.org/products/specify-7/
GNU General Public License v2.0
59 stars 38 forks source link

`-to-many` subviews showing in a form by default instead of a grid #4878

Closed grantfitzsimmons closed 1 week ago

grantfitzsimmons commented 2 weeks ago

Describe the bug For example, "Collection Object Properties" and "Collectors" are now shown in form view rather than in a grid

To Reproduce Steps to reproduce the behavior:

  1. Go to any database using the default forms
  2. Go to Collecting Event
  3. See that the Collectors subview is appearing as a form rather than a grid (compare v7.9.3.1 to edge)

Expected behavior Collectors (and other -to-many rels) should be shown in grid view automatically rather than in form view just as Preparations is.

Screenshots Same form definition:

image
image
        <row>
            <cell type="subview" viewname="Collectors" id="12" name="collectors" colspan="12" rows="3"/>
        </row>

See that the defaulttype="" attribute is not present in the default forms in Specify 6 despite appearing like a table in the app.

Paleo Views.xml.zip

Crash Report

Specify 7 System Information - 2024-05-02T19_37_05.676Z.txt

bauerjen in the "University of Michigan Invertebrate Paleontology" collection

sharadsw commented 2 weeks ago

Looks like this switched to subview when fixing a different issue in #4776, specifically here: 2955b5a4fd4148e712b4609eb03c574499aca9d0

melton-jason commented 2 weeks ago

The issue was caused as a result of https://github.com/specify/specify7/pull/4776.

The changes in #4776 can be seen as in how Specify returns the ViewDescription from fetchView, as summarized in a case below:

The Collectors ViewDescription (after fetchView('Collectors')) prior to #4776

{
    "name": "Collectors",
    "class": "edu.ku.brc.specify.datamodel.Collector",
    "busrules": "edu.ku.brc.specify.datamodel.busrules.CollectorBusRules",
    "altviews": {
        "Collectors Table View": {
            "name": "Collectors Table View",
            "viewdef": "Collectors Table",
            "mode": "view"
        },
        "Collectors Table Edit": {
            "name": "Collectors Table Edit",
            "viewdef": "Collectors Table",
            "mode": "edit",
            "default": "true"
        }
    },
    "viewdefs": {
        "Collectors Table": "<viewdef type=\"formtable\" name=\"Collectors Table\" class=\"edu.ku.brc.specify.datamodel.Collector\" gettable=\"edu.ku.brc.af.ui.forms.DataGetterForObj\" settable=\"edu.ku.brc.af.ui.forms.DataSetterForObj\">\n            <desc>Collectors grid view for CollectingEvent form.</desc>\n            <definition>Collectors</definition>\n        </viewdef>\n\n        ",
        "Collectors": "<viewdef type=\"form\" name=\"Collectors\" class=\"edu.ku.brc.specify.datamodel.Collector\" gettable=\"edu.ku.brc.af.ui.forms.DataGetterForObj\" settable=\"edu.ku.brc.af.ui.forms.DataSetterForObj\">\n            <desc>The Collectors form - UNKNOWN use in database.</desc>\n            <enableRules />\n\n            <columnDef>p,5dlu,p:g,5dlu,p</columnDef>\n            <rowDef auto=\"true\" cell=\"p\" sep=\"2px\" />\n\n            <rows>\n                <row>\n                    <cell type=\"label\" labelfor=\"3\" />\n                    <cell type=\"field\" id=\"3\" name=\"agent.lastName\" uitype=\"text\" colspan=\"3\" />\n                </row>\n                <row>\n                    <cell type=\"label\" labelfor=\"5\" />\n                    <cell type=\"field\" id=\"5\" name=\"agent.firstName\" uitype=\"text\" colspan=\"3\" />\n                </row>\n                <row>\n                    <cell type=\"label\" labelfor=\"7\" />\n                    <cell type=\"field\" id=\"7\" name=\"remarks\" uitype=\"text\" colspan=\"3\" />\n                </row>\n            </rows>\n        </viewdef>\n\n        "
    },
    "view": "<view name=\"Collectors\" class=\"edu.ku.brc.specify.datamodel.Collector\" busrules=\"edu.ku.brc.specify.datamodel.busrules.CollectorBusRules\">\n            <desc>The Collectors Subform.</desc>\n            <altviews>\n                <!-- <altview name=\"Collectors Icon View\"  viewdef=\"CollectorsIconView\" mode=\"view\"/>\n                <altview name=\"Collectors Icon Edit\"  viewdef=\"CollectorsIconView\" mode=\"edit\"/>  -->\n                <altview name=\"Collectors Table View\" viewdef=\"Collectors Table\" mode=\"view\" />\n                <altview name=\"Collectors Table Edit\" viewdef=\"Collectors Table\" mode=\"edit\" default=\"true\" />\n            </altviews>\n        </view>\n\n        ",
    "viewsetName": "Common",
    "viewsetLevel": "Common",
    "viewsetSource": "disk",
    "viewsetId": null,
    "viewsetFile": "common/common.views.xml"
}

The Collectors ViewDescription (after fetchView('Collectors')) after #4776

{
   "name":"Collectors",
   "class":"edu.ku.brc.specify.datamodel.Collector",
   "busrules":"edu.ku.brc.specify.datamodel.busrules.CollectorBusRules",
   "altviews":{
      "Collectors Table View":{
         "name":"Collectors Table View",
         "viewdef":"Collectors Table",
         "mode":"view"
      },
      "Collectors Table Edit":{
         "name":"Collectors Table Edit",
         "viewdef":"Collectors Table",
         "mode":"edit",
         "default":"true"
      },
      "Collector View":{
         "name":"Collector View",
         "viewdef":"Collector",
         "mode":"view",
         "validated":"false",
         "default":"true"
      },
      "Collector Edit":{
         "name":"Collector Edit",
         "viewdef":"Collector",
         "mode":"edit",
         "validated":"true"
      }
   },
   "viewdefs":{
      "Collectors Table":"<viewdef type=\"formtable\" name=\"Collectors Table\" class=\"edu.ku.brc.specify.datamodel.Collector\" gettable=\"edu.ku.brc.af.ui.forms.DataGetterForObj\" settable=\"edu.ku.brc.af.ui.forms.DataSetterForObj\">\n            <desc>Collectors grid view for CollectingEvent form.</desc>\n            <definition>Collectors</definition>\n        </viewdef>\n\n        ",
      "Collectors":"<viewdef type=\"form\" name=\"Collectors\" class=\"edu.ku.brc.specify.datamodel.Collector\" gettable=\"edu.ku.brc.af.ui.forms.DataGetterForObj\" settable=\"edu.ku.brc.af.ui.forms.DataSetterForObj\">\n            <desc>The Collectors form - UNKNOWN use in database.</desc>\n            <enableRules />\n\n            <columnDef>p,5dlu,p:g,5dlu,p</columnDef>\n            <rowDef auto=\"true\" cell=\"p\" sep=\"2px\" />\n\n            <rows>\n                <row>\n                    <cell type=\"label\" labelfor=\"3\" />\n                    <cell type=\"field\" id=\"3\" name=\"agent.lastName\" uitype=\"text\" colspan=\"3\" />\n                </row>\n                <row>\n                    <cell type=\"label\" labelfor=\"5\" />\n                    <cell type=\"field\" id=\"5\" name=\"agent.firstName\" uitype=\"text\" colspan=\"3\" />\n                </row>\n                <row>\n                    <cell type=\"label\" labelfor=\"7\" />\n                    <cell type=\"field\" id=\"7\" name=\"remarks\" uitype=\"text\" colspan=\"3\" />\n                </row>\n            </rows>\n        </viewdef>\n\n        ",
      "Collector":"<viewdef type=\"form\" name=\"Collector\" class=\"edu.ku.brc.specify.datamodel.Collector\" gettable=\"edu.ku.brc.af.ui.forms.DataGetterForObj\" settable=\"edu.ku.brc.af.ui.forms.DataSetterForObj\">\n            <desc>The Collector form.</desc>\n            <enableRules />\n\n            <columnDef>105px,2px,210px,5px,100px,2px,98px,300px,p:g</columnDef>\n            <columnDef os=\"lnx\">135px,2px,230px,5px,120px,2px,118px,325px,p:g</columnDef>\n            <columnDef os=\"mac\">130px,2px,215px,5px,140px,2px,138px,395px,p:g</columnDef>\n            <columnDef os=\"exp\">p,2px,p:g,5px:g,p,2px,p:g,p:g(2),p:g</columnDef>\n            <rowDef>p,2dlu,p:g,2dlu,p:g</rowDef>\n\n            <rows>\n                <row>\n                    <cell type=\"label\" labelfor=\"1\" />\n                    <cell type=\"field\" id=\"1\" name=\"agent\" uitype=\"querycbx\" initialize=\"name=Agent;title=Agent\" />\n                </row>\n                <row>\n                    <cell type=\"label\" labelfor=\"3\" />\n                    <cell type=\"field\" id=\"3\" name=\"remarks\" uitype=\"textareabrief\" rows=\"2\" colspan=\"6\" />\n                </row>\n                <!--<row>\n                    <cell type=\"label\" labelfor=\"9\"/>\n                    <cell type=\"field\" id=\"9\" name=\"createdByAgent\" uitype=\"label\" readonly=\"true\"  uifieldformatter=\"Agent\"/>\n                    <cell type=\"label\" labelfor=\"10\"/>\n                    <cell type=\"field\" id=\"10\" name=\"modifiedByAgent\" uitype=\"label\" readonly=\"true\"  uifieldformatter=\"Agent\"/>\n                    <cell type=\"label\" id=\"divLabel\" label=\" \" initialize=\"align=right\"/>\n                    <cell type=\"field\" id=\"4\" name=\"divisionCBX\" uitype=\"combobox\" ignore=\"true\"/>\n                </row>\n                <row>\n                \t<cell type=\"field\" id=\"34\" uitype=\"checkbox\" name=\"isPrimary\"/>                \t\n                </row>\n                <row>\n                    <cell type=\"label\" labelfor=\"11\"/>\n                    <cell type=\"field\" id=\"11\" name=\"timestampModified\" uitype=\"label\" readonly=\"true\"/>\n                    <cell type=\"label\" labelfor=\"12\"/>\n                    <cell type=\"field\" id=\"12\" name=\"timestampCreated\" uitype=\"label\" readonly=\"true\"/>\n                </row>-->\n            </rows>\n        </viewdef>\n\n        "
   },
   "view":"<view name=\"Collectors\" class=\"edu.ku.brc.specify.datamodel.Collector\" busrules=\"edu.ku.brc.specify.datamodel.busrules.CollectorBusRules\">\n            <desc>The Collectors Subform.</desc>\n            <altviews>\n                <!-- <altview name=\"Collectors Icon View\"  viewdef=\"CollectorsIconView\" mode=\"view\"/>\n                <altview name=\"Collectors Icon Edit\"  viewdef=\"CollectorsIconView\" mode=\"edit\"/>  -->\n                <altview name=\"Collectors Table View\" viewdef=\"Collectors Table\" mode=\"view\" />\n                <altview name=\"Collectors Table Edit\" viewdef=\"Collectors Table\" mode=\"edit\" default=\"true\" />\n            </altviews>\n        </view>\n\n        ",
   "viewsetName":"Common",
   "viewsetLevel":"Common",
   "viewsetSource":"disk",
   "viewsetId":null,
   "viewsetFile":"common/common.views.xml"
}

Every reference of using the Collectors (an example shown below) view as a subview does not specify a defaulttype for the view

<cell type="subview" viewname="Collectors" id="5" name="collectors" colspan="13" rows="3"/>

https://github.com/search?q=repo%3Aspecify%2Fspecify6+viewname%3D%22Collectors%22&type=code&p=1

Specify assumes the default type of subviews which do not explicitly have defaulttype defined in the cell to match the form mode of the parent. Which, if rendering a base form is defined as form https://github.com/specify/specify7/blob/5e6555b57a228d2ca9d008f323396b01b332274f/specifyweb/frontend/js_src/lib/components/Forms/BaseResourceView.tsx#L86-L102


The cause of #4878 and why this was not an issue prior to #4776 is because of how Specify handles resolving the correct view definition given a mode and form type:

https://github.com/specify/specify7/blob/5e6555b57a228d2ca9d008f323396b01b332274f/specifyweb/frontend/js_src/lib/components/FormParse/index.ts#L344-L360

Recall the Collectors view prior to #4776. If the mode is edit, then altViews will only be [Collectors Table Edit]. Because the view definition referenced by Collectors Table Edit has type=formtable, and the form type that the function is looking for is form, altviewwill return undefined and Specify will use the first altview and its viewdef defined in altviews. This happens to be the definition for for Collectors Table.

After #4776, there is an altview which points to a view definition with type="Form", so Specify is always using that altview and viewdefinition