Skip to content

Technical docs

Views#

Django views.

PeerReviewView#

View for the reviewer role of the Open Peer Review process.

Bases: LoginRequiredMixin, View

A view handling the peer review of metadata. This view supports loading, parsing, sorting metadata, and handling GET and POST requests for peer review.

Source code in dataedit/views.py
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
class PeerReviewView(LoginRequiredMixin, View):
    """
    A view handling the peer review of metadata. This view supports loading,
    parsing, sorting metadata, and handling GET and POST requests for peer review.
    """

    def load_json(self, schema, table, review_id=None):
        """
        Load JSON metadata from the database. If the review_id is available
        then load the metadata form the peer review instance and not from the
        table. This avoids changes to the metadata that is or was reviewed.

        Args:
            schema (str): The schema of the table.
            table (str): The name of the table.
            review_id (int): Id of a peer review in the django database

        Returns:
            dict: Loaded oemetadata.
        """
        metadata = {}
        if review_id is None:
            metadata = load_metadata_from_db(schema, table)
        elif review_id:
            opr = PeerReviewManager.filter_opr_by_id(opr_id=review_id)
            metadata = opr.oemetadata

        return metadata

    def load_json_schema(self):
        """
        Load the JSON schema used for validating metadata.

        Note:
            Update this method if a new oemetadata version is released.

        Returns:
            dict: JSON schema.
        """
        json_schema = OEMETADATA_V160_SCHEMA
        return json_schema

    def parse_keys(self, val, old=""):
        """
        Recursively parse keys from a nested dictionary or list and return them
        as a list of dictionaries.

        Args:
            val (dict or list): The input dictionary or list to parse.
            old (str, optional): The prefix for nested keys. Defaults to an
                empty string.

        Returns:
            list: A list of dictionaries, each containing 'field' and 'value'
                keys.
        """
        lines = []
        if isinstance(val, dict):
            for k in val.keys():
                lines += self.parse_keys(val[k], old + "." + str(k))
        elif isinstance(val, list):
            if not val:
                # handles empty list
                lines += [{"field": old[1:], "value": str(val)}]
                # pass
            else:
                for i, k in enumerate(val):
                    lines += self.parse_keys(
                        k, old + "." + str(i)
                    )  # handles user value
        else:
            lines += [{"field": old[1:], "value": str(val)}]
        return lines

    def sort_in_category(self, schema, table, oemetadata):
        """
        Sorts the metadata of a table into categories and adds the value
        suggestion and comment that were added during the review, to facilitate
        Further processing easier.

        Note:
            The categories spatial & temporal are often combined during visualization.

        Args:
            schema (str): The schema of the table.
            table (str): The name of the table.

        Returns:


        Examples:
            A return value can look like the below dictionary:

            >>>
            {
                "general": [
                    {
                    "field": "id",
                    "value": "http: //127.0.0.1:8000/dataedit/view/model_draft/test2",
                    "newValue": "",
                    "reviewer_suggestion": "",
                    "suggestion_comment": ""
                    }
                ],
                "spatial": [...],
                "temporal": [...],
                "source": [...],
                "license": [...],
            }

        """

        val = self.parse_keys(oemetadata)
        gen_key_list = []
        spatial_key_list = []
        temporal_key_list = []
        source_key_list = []
        license_key_list = []

        for i in val:
            fieldKey = list(i.values())[0]
            if fieldKey.split(".")[0] == "spatial":
                spatial_key_list.append(i)
            elif fieldKey.split(".")[0] == "temporal":
                temporal_key_list.append(i)
            elif fieldKey.split(".")[0] == "sources":
                source_key_list.append(i)
            elif fieldKey.split(".")[0] == "licenses":
                license_key_list.append(i)

            elif (
                fieldKey.split(".")[0] == "name"
                or fieldKey.split(".")[0] == "title"
                or fieldKey.split(".")[0] == "id"
                or fieldKey.split(".")[0] == "description"
                or fieldKey.split(".")[0] == "language"
                or fieldKey.split(".")[0] == "subject"
                or fieldKey.split(".")[0] == "keywords"
                or fieldKey.split(".")[0] == "publicationDate"
                or fieldKey.split(".")[0] == "context"
            ):
                gen_key_list.append(i)

        meta = {
            "general": gen_key_list,
            "spatial": spatial_key_list,
            "temporal": temporal_key_list,
            "source": source_key_list,
            "license": license_key_list,
        }

        return meta

    def get_all_field_descriptions(self, json_schema, prefix=""):
        """
        Collects the field title, descriptions, examples, and badge information
        for each field of the oemetadata from the JSON schema and prepares them
        for further processing.

        Args:
            json_schema (dict): The JSON schema to extract field descriptions
                from.
            prefix (str, optional): The prefix for nested keys. Defaults to an
                empty string.

        Returns:
            dict: A dictionary containing field descriptions, examples, and
                other information.
        """

        field_descriptions = {}

        def extract_descriptions(properties, prefix=""):
            for field, value in properties.items():
                key = f"{prefix}.{field}" if prefix else field

                if any(
                    attr in value
                    for attr in ["description", "example", "badge", "title"]
                ):
                    field_descriptions[key] = {}
                    if "description" in value:
                        field_descriptions[key]["description"] = value["description"]
                    if "example" in value:
                        field_descriptions[key]["example"] = value["example"]
                    if "badge" in value:
                        field_descriptions[key]["badge"] = value["badge"]
                    if "title" in value:
                        field_descriptions[key]["title"] = value["title"]
                if "properties" in value:
                    new_prefix = f"{prefix}.{field}" if prefix else field
                    extract_descriptions(value["properties"], new_prefix)
                if "items" in value:
                    new_prefix = f"{prefix}.{field}" if prefix else field
                    if "properties" in value["items"]:
                        extract_descriptions(value["items"]["properties"], new_prefix)

        extract_descriptions(json_schema["properties"], prefix)
        return field_descriptions

    def get(self, request, schema, table, review_id=None):
        """
        Handle GET requests for peer review.
        Loads necessary data and renders the review template.

        Args:
            request (HttpRequest): The incoming HTTP GET request.
            schema (str): The schema of the table.
            table (str): The name of the table.
            review_id (int, optional): The ID of the review. Defaults to None.

        Returns:
            HttpResponse: Rendered HTML response.
        """
        # review_state = PeerReview.is_finished  # TODO: Use later
        json_schema = self.load_json_schema()
        can_add = False
        table_obj = Table.load(schema, table)
        field_descriptions = self.get_all_field_descriptions(json_schema)

        # Check user permissions
        if not request.user.is_anonymous:
            level = request.user.get_table_permission_level(table_obj)
            can_add = level >= login_models.WRITE_PERM

        oemetadata = self.load_json(schema, table, review_id)
        metadata = self.sort_in_category(
            schema, table, oemetadata=oemetadata
        )  # Generate URL for peer_review_reviewer
        if review_id is not None:
            url_peer_review = reverse(
                "dataedit:peer_review_reviewer",
                kwargs={"schema": schema, "table": table, "review_id": review_id},
            )
            opr_review = PeerReviewManager.filter_opr_by_id(opr_id=review_id)
            existing_review = opr_review.review.get("reviews", [])
            review_finished = opr_review.is_finished
            categories = [
                "general",
                "spatial",
                "temporal",
                "source",
                "license",
            ]
            state_dict = process_review_data(
                review_data=existing_review, metadata=metadata, categories=categories
            )
        else:
            url_peer_review = reverse(
                "dataedit:peer_review_create", kwargs={"schema": schema, "table": table}
            )
            # existing_review={}
            state_dict = None
            review_finished = None

        config_data = {
            "can_add": can_add,
            "url_peer_review": url_peer_review,
            "url_table": reverse(
                "dataedit:view", kwargs={"schema": schema, "table": table}
            ),
            "topic": schema,
            "table": table,
            "review_finished": review_finished,
        }
        context_meta = {
            # need this here as json.dumps breaks the template syntax access
            # like {{ config.table }} now you can use {{ table }}
            "table": table,
            "topic": schema,
            "config": json.dumps(config_data),
            "meta": metadata,
            "json_schema": json_schema,
            "field_descriptions_json": json.dumps(field_descriptions),
            "state_dict": json.dumps(state_dict),
        }
        return render(request, "dataedit/opr_review.html", context=context_meta)

    def post(self, request, schema, table, review_id=None):
        """
        Handle POST requests for submitting reviews by the reviewer.

        This method:
        - Creates (or saves) reviews in the PeerReview table.
        - Updates the review finished attribute in the dataedit.Tables table,
            indicating that the table can be moved from the model draft topic.

        Missing parts:
        - once the opr is finished (all field reviews agreed on)
        - merge field review results to metadata on table
        - awarde a badge
            - is field filled in?
            - calculate the badge by comparing filled fields
              and the badges form metadata schema

        Args:
            request (HttpRequest): The incoming HTTP POST request.
            schema (str): The schema of the table.
            table (str): The name of the table.
            review_id (int, optional): The ID of the review. Defaults to None.

        Returns:
            HttpResponse: Rendered HTML response for the review.

        Raises:
            JsonResponse: If any error occurs, a JsonResponse containing the
            error message is raised.

        Note:
            - There are some missing parts in this method. Once the review process
                is finished (all field reviews agreed on), it should merge field
                review results to metadata on the table and award a badge based
                on certain criteria.
            - A notification should be sent to the user if he/she can't review tables
            for which he/she is the table holder (TODO).
            - After a review is finished, the table's metadata is updated, and the table
            can be moved to a different schema or topic (TODO).
        """
        context = {}
        if request.method == "POST":
            # get the review data and additional application metadata
            # from user peer review submit/save
            review_data = json.loads(request.body)
            if review_id:
                contributor_review = PeerReview.objects.filter(id=review_id).first()
                if contributor_review:
                    contributor_review_data = contributor_review.review.get(
                        "reviews", []
                    )
                    review_data["reviewData"]["reviews"].extend(contributor_review_data)

            # The type can be "save" or "submit" as this triggers different behavior
            review_post_type = review_data.get("reviewType")
            # The opr datamodel that includes the field review data and metadata
            review_datamodel = review_data.get("reviewData")
            review_finished = review_datamodel.get("reviewFinished")
            # TODO: Send a notification to the user that he can't review tables
            # he is the table holder.
            contributor = PeerReviewManager.load_contributor(schema, table)

            if contributor is not None:
                # Überprüfen, ob ein aktiver PeerReview existiert
                active_peer_review = PeerReview.load(schema=schema, table=table)
                if active_peer_review is None or active_peer_review.is_finished:
                    # Kein aktiver PeerReview vorhanden
                    # oder der aktive PeerReview ist abgeschlossen
                    table_review = PeerReview(
                        schema=schema,
                        table=table,
                        is_finished=review_finished,
                        review=review_datamodel,
                        reviewer=request.user,
                        contributor=contributor,
                        oemetadata=load_metadata_from_db(schema=schema, table=table),
                    )
                    table_review.save(review_type=review_post_type)
                else:
                    # Aktiver PeerReview ist vorhanden ... aktualisieren
                    current_review_data = active_peer_review.review
                    merged_review_data = merge_field_reviews(
                        current_json=current_review_data, new_json=review_datamodel
                    )

                    # Set new review values and update existing review
                    active_peer_review.review = merged_review_data
                    active_peer_review.reviewer = request.user
                    active_peer_review.contributor = contributor
                    active_peer_review.update(review_type=review_post_type)
            else:
                error_msg = (
                    "Failed to retrieve any user that identifies "
                    f"as table holder for the current table: {table}!"
                )
                return JsonResponse({"error": error_msg}, status=400)

            # TODO: Check for schema/topic as reviewed finished also indicates the table
            # needs to be or has to be moved.
            if review_finished is True:
                review_table = Table.load(schema=schema, table=table)
                review_table.set_is_reviewed()
                metadata = self.load_json(schema, table, review_id=review_id)

                recursive_update(metadata, review_data)

                save_metadata_to_db(schema, table, metadata)

                if active_peer_review:
                    # Update the oemetadata in the active PeerReview
                    active_peer_review.oemetadata = metadata
                    active_peer_review.save()

                # TODO: also update reviewFinished in review datamodel json
                # logging.INFO(f"Table {table.name} is now reviewed and can be moved
                # to the destination schema.")

        return render(request, "dataedit/opr_review.html", context=context)

get(request, schema, table, review_id=None) #

Handle GET requests for peer review. Loads necessary data and renders the review template.

Parameters:

Name Type Description Default
request HttpRequest

The incoming HTTP GET request.

required
schema str

The schema of the table.

required
table str

The name of the table.

required
review_id int

The ID of the review. Defaults to None.

None

Returns:

Name Type Description
HttpResponse

Rendered HTML response.

Source code in dataedit/views.py
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
def get(self, request, schema, table, review_id=None):
    """
    Handle GET requests for peer review.
    Loads necessary data and renders the review template.

    Args:
        request (HttpRequest): The incoming HTTP GET request.
        schema (str): The schema of the table.
        table (str): The name of the table.
        review_id (int, optional): The ID of the review. Defaults to None.

    Returns:
        HttpResponse: Rendered HTML response.
    """
    # review_state = PeerReview.is_finished  # TODO: Use later
    json_schema = self.load_json_schema()
    can_add = False
    table_obj = Table.load(schema, table)
    field_descriptions = self.get_all_field_descriptions(json_schema)

    # Check user permissions
    if not request.user.is_anonymous:
        level = request.user.get_table_permission_level(table_obj)
        can_add = level >= login_models.WRITE_PERM

    oemetadata = self.load_json(schema, table, review_id)
    metadata = self.sort_in_category(
        schema, table, oemetadata=oemetadata
    )  # Generate URL for peer_review_reviewer
    if review_id is not None:
        url_peer_review = reverse(
            "dataedit:peer_review_reviewer",
            kwargs={"schema": schema, "table": table, "review_id": review_id},
        )
        opr_review = PeerReviewManager.filter_opr_by_id(opr_id=review_id)
        existing_review = opr_review.review.get("reviews", [])
        review_finished = opr_review.is_finished
        categories = [
            "general",
            "spatial",
            "temporal",
            "source",
            "license",
        ]
        state_dict = process_review_data(
            review_data=existing_review, metadata=metadata, categories=categories
        )
    else:
        url_peer_review = reverse(
            "dataedit:peer_review_create", kwargs={"schema": schema, "table": table}
        )
        # existing_review={}
        state_dict = None
        review_finished = None

    config_data = {
        "can_add": can_add,
        "url_peer_review": url_peer_review,
        "url_table": reverse(
            "dataedit:view", kwargs={"schema": schema, "table": table}
        ),
        "topic": schema,
        "table": table,
        "review_finished": review_finished,
    }
    context_meta = {
        # need this here as json.dumps breaks the template syntax access
        # like {{ config.table }} now you can use {{ table }}
        "table": table,
        "topic": schema,
        "config": json.dumps(config_data),
        "meta": metadata,
        "json_schema": json_schema,
        "field_descriptions_json": json.dumps(field_descriptions),
        "state_dict": json.dumps(state_dict),
    }
    return render(request, "dataedit/opr_review.html", context=context_meta)

get_all_field_descriptions(json_schema, prefix='') #

Collects the field title, descriptions, examples, and badge information for each field of the oemetadata from the JSON schema and prepares them for further processing.

Parameters:

Name Type Description Default
json_schema dict

The JSON schema to extract field descriptions from.

required
prefix str

The prefix for nested keys. Defaults to an empty string.

''

Returns:

Name Type Description
dict

A dictionary containing field descriptions, examples, and other information.

Source code in dataedit/views.py
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
def get_all_field_descriptions(self, json_schema, prefix=""):
    """
    Collects the field title, descriptions, examples, and badge information
    for each field of the oemetadata from the JSON schema and prepares them
    for further processing.

    Args:
        json_schema (dict): The JSON schema to extract field descriptions
            from.
        prefix (str, optional): The prefix for nested keys. Defaults to an
            empty string.

    Returns:
        dict: A dictionary containing field descriptions, examples, and
            other information.
    """

    field_descriptions = {}

    def extract_descriptions(properties, prefix=""):
        for field, value in properties.items():
            key = f"{prefix}.{field}" if prefix else field

            if any(
                attr in value
                for attr in ["description", "example", "badge", "title"]
            ):
                field_descriptions[key] = {}
                if "description" in value:
                    field_descriptions[key]["description"] = value["description"]
                if "example" in value:
                    field_descriptions[key]["example"] = value["example"]
                if "badge" in value:
                    field_descriptions[key]["badge"] = value["badge"]
                if "title" in value:
                    field_descriptions[key]["title"] = value["title"]
            if "properties" in value:
                new_prefix = f"{prefix}.{field}" if prefix else field
                extract_descriptions(value["properties"], new_prefix)
            if "items" in value:
                new_prefix = f"{prefix}.{field}" if prefix else field
                if "properties" in value["items"]:
                    extract_descriptions(value["items"]["properties"], new_prefix)

    extract_descriptions(json_schema["properties"], prefix)
    return field_descriptions

load_json(schema, table, review_id=None) #

Load JSON metadata from the database. If the review_id is available then load the metadata form the peer review instance and not from the table. This avoids changes to the metadata that is or was reviewed.

Parameters:

Name Type Description Default
schema str

The schema of the table.

required
table str

The name of the table.

required
review_id int

Id of a peer review in the django database

None

Returns:

Name Type Description
dict

Loaded oemetadata.

Source code in dataedit/views.py
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
def load_json(self, schema, table, review_id=None):
    """
    Load JSON metadata from the database. If the review_id is available
    then load the metadata form the peer review instance and not from the
    table. This avoids changes to the metadata that is or was reviewed.

    Args:
        schema (str): The schema of the table.
        table (str): The name of the table.
        review_id (int): Id of a peer review in the django database

    Returns:
        dict: Loaded oemetadata.
    """
    metadata = {}
    if review_id is None:
        metadata = load_metadata_from_db(schema, table)
    elif review_id:
        opr = PeerReviewManager.filter_opr_by_id(opr_id=review_id)
        metadata = opr.oemetadata

    return metadata

load_json_schema() #

Load the JSON schema used for validating metadata.

Note

Update this method if a new oemetadata version is released.

Returns:

Name Type Description
dict

JSON schema.

Source code in dataedit/views.py
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
def load_json_schema(self):
    """
    Load the JSON schema used for validating metadata.

    Note:
        Update this method if a new oemetadata version is released.

    Returns:
        dict: JSON schema.
    """
    json_schema = OEMETADATA_V160_SCHEMA
    return json_schema

parse_keys(val, old='') #

Recursively parse keys from a nested dictionary or list and return them as a list of dictionaries.

Parameters:

Name Type Description Default
val dict or list

The input dictionary or list to parse.

required
old str

The prefix for nested keys. Defaults to an empty string.

''

Returns:

Name Type Description
list

A list of dictionaries, each containing 'field' and 'value' keys.

Source code in dataedit/views.py
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
def parse_keys(self, val, old=""):
    """
    Recursively parse keys from a nested dictionary or list and return them
    as a list of dictionaries.

    Args:
        val (dict or list): The input dictionary or list to parse.
        old (str, optional): The prefix for nested keys. Defaults to an
            empty string.

    Returns:
        list: A list of dictionaries, each containing 'field' and 'value'
            keys.
    """
    lines = []
    if isinstance(val, dict):
        for k in val.keys():
            lines += self.parse_keys(val[k], old + "." + str(k))
    elif isinstance(val, list):
        if not val:
            # handles empty list
            lines += [{"field": old[1:], "value": str(val)}]
            # pass
        else:
            for i, k in enumerate(val):
                lines += self.parse_keys(
                    k, old + "." + str(i)
                )  # handles user value
    else:
        lines += [{"field": old[1:], "value": str(val)}]
    return lines

post(request, schema, table, review_id=None) #

Handle POST requests for submitting reviews by the reviewer.

This method: - Creates (or saves) reviews in the PeerReview table. - Updates the review finished attribute in the dataedit.Tables table, indicating that the table can be moved from the model draft topic.

Missing parts: - once the opr is finished (all field reviews agreed on) - merge field review results to metadata on table - awarde a badge - is field filled in? - calculate the badge by comparing filled fields and the badges form metadata schema

Parameters:

Name Type Description Default
request HttpRequest

The incoming HTTP POST request.

required
schema str

The schema of the table.

required
table str

The name of the table.

required
review_id int

The ID of the review. Defaults to None.

None

Returns:

Name Type Description
HttpResponse

Rendered HTML response for the review.

Raises:

Type Description
JsonResponse

If any error occurs, a JsonResponse containing the

Note
  • There are some missing parts in this method. Once the review process is finished (all field reviews agreed on), it should merge field review results to metadata on the table and award a badge based on certain criteria.
  • A notification should be sent to the user if he/she can't review tables for which he/she is the table holder (TODO).
  • After a review is finished, the table's metadata is updated, and the table can be moved to a different schema or topic (TODO).
Source code in dataedit/views.py
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
def post(self, request, schema, table, review_id=None):
    """
    Handle POST requests for submitting reviews by the reviewer.

    This method:
    - Creates (or saves) reviews in the PeerReview table.
    - Updates the review finished attribute in the dataedit.Tables table,
        indicating that the table can be moved from the model draft topic.

    Missing parts:
    - once the opr is finished (all field reviews agreed on)
    - merge field review results to metadata on table
    - awarde a badge
        - is field filled in?
        - calculate the badge by comparing filled fields
          and the badges form metadata schema

    Args:
        request (HttpRequest): The incoming HTTP POST request.
        schema (str): The schema of the table.
        table (str): The name of the table.
        review_id (int, optional): The ID of the review. Defaults to None.

    Returns:
        HttpResponse: Rendered HTML response for the review.

    Raises:
        JsonResponse: If any error occurs, a JsonResponse containing the
        error message is raised.

    Note:
        - There are some missing parts in this method. Once the review process
            is finished (all field reviews agreed on), it should merge field
            review results to metadata on the table and award a badge based
            on certain criteria.
        - A notification should be sent to the user if he/she can't review tables
        for which he/she is the table holder (TODO).
        - After a review is finished, the table's metadata is updated, and the table
        can be moved to a different schema or topic (TODO).
    """
    context = {}
    if request.method == "POST":
        # get the review data and additional application metadata
        # from user peer review submit/save
        review_data = json.loads(request.body)
        if review_id:
            contributor_review = PeerReview.objects.filter(id=review_id).first()
            if contributor_review:
                contributor_review_data = contributor_review.review.get(
                    "reviews", []
                )
                review_data["reviewData"]["reviews"].extend(contributor_review_data)

        # The type can be "save" or "submit" as this triggers different behavior
        review_post_type = review_data.get("reviewType")
        # The opr datamodel that includes the field review data and metadata
        review_datamodel = review_data.get("reviewData")
        review_finished = review_datamodel.get("reviewFinished")
        # TODO: Send a notification to the user that he can't review tables
        # he is the table holder.
        contributor = PeerReviewManager.load_contributor(schema, table)

        if contributor is not None:
            # Überprüfen, ob ein aktiver PeerReview existiert
            active_peer_review = PeerReview.load(schema=schema, table=table)
            if active_peer_review is None or active_peer_review.is_finished:
                # Kein aktiver PeerReview vorhanden
                # oder der aktive PeerReview ist abgeschlossen
                table_review = PeerReview(
                    schema=schema,
                    table=table,
                    is_finished=review_finished,
                    review=review_datamodel,
                    reviewer=request.user,
                    contributor=contributor,
                    oemetadata=load_metadata_from_db(schema=schema, table=table),
                )
                table_review.save(review_type=review_post_type)
            else:
                # Aktiver PeerReview ist vorhanden ... aktualisieren
                current_review_data = active_peer_review.review
                merged_review_data = merge_field_reviews(
                    current_json=current_review_data, new_json=review_datamodel
                )

                # Set new review values and update existing review
                active_peer_review.review = merged_review_data
                active_peer_review.reviewer = request.user
                active_peer_review.contributor = contributor
                active_peer_review.update(review_type=review_post_type)
        else:
            error_msg = (
                "Failed to retrieve any user that identifies "
                f"as table holder for the current table: {table}!"
            )
            return JsonResponse({"error": error_msg}, status=400)

        # TODO: Check for schema/topic as reviewed finished also indicates the table
        # needs to be or has to be moved.
        if review_finished is True:
            review_table = Table.load(schema=schema, table=table)
            review_table.set_is_reviewed()
            metadata = self.load_json(schema, table, review_id=review_id)

            recursive_update(metadata, review_data)

            save_metadata_to_db(schema, table, metadata)

            if active_peer_review:
                # Update the oemetadata in the active PeerReview
                active_peer_review.oemetadata = metadata
                active_peer_review.save()

            # TODO: also update reviewFinished in review datamodel json
            # logging.INFO(f"Table {table.name} is now reviewed and can be moved
            # to the destination schema.")

    return render(request, "dataedit/opr_review.html", context=context)

sort_in_category(schema, table, oemetadata) #

Sorts the metadata of a table into categories and adds the value suggestion and comment that were added during the review, to facilitate Further processing easier.

Note

The categories spatial & temporal are often combined during visualization.

Parameters:

Name Type Description Default
schema str

The schema of the table.

required
table str

The name of the table.

required

Returns:

Examples:

A return value can look like the below dictionary:

>>>
{
    "general": [
        {
        "field": "id",
        "value": "http: //127.0.0.1:8000/dataedit/view/model_draft/test2",
        "newValue": "",
        "reviewer_suggestion": "",
        "suggestion_comment": ""
        }
    ],
    "spatial": [...],
    "temporal": [...],
    "source": [...],
    "license": [...],
}
Source code in dataedit/views.py
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
def sort_in_category(self, schema, table, oemetadata):
    """
    Sorts the metadata of a table into categories and adds the value
    suggestion and comment that were added during the review, to facilitate
    Further processing easier.

    Note:
        The categories spatial & temporal are often combined during visualization.

    Args:
        schema (str): The schema of the table.
        table (str): The name of the table.

    Returns:


    Examples:
        A return value can look like the below dictionary:

        >>>
        {
            "general": [
                {
                "field": "id",
                "value": "http: //127.0.0.1:8000/dataedit/view/model_draft/test2",
                "newValue": "",
                "reviewer_suggestion": "",
                "suggestion_comment": ""
                }
            ],
            "spatial": [...],
            "temporal": [...],
            "source": [...],
            "license": [...],
        }

    """

    val = self.parse_keys(oemetadata)
    gen_key_list = []
    spatial_key_list = []
    temporal_key_list = []
    source_key_list = []
    license_key_list = []

    for i in val:
        fieldKey = list(i.values())[0]
        if fieldKey.split(".")[0] == "spatial":
            spatial_key_list.append(i)
        elif fieldKey.split(".")[0] == "temporal":
            temporal_key_list.append(i)
        elif fieldKey.split(".")[0] == "sources":
            source_key_list.append(i)
        elif fieldKey.split(".")[0] == "licenses":
            license_key_list.append(i)

        elif (
            fieldKey.split(".")[0] == "name"
            or fieldKey.split(".")[0] == "title"
            or fieldKey.split(".")[0] == "id"
            or fieldKey.split(".")[0] == "description"
            or fieldKey.split(".")[0] == "language"
            or fieldKey.split(".")[0] == "subject"
            or fieldKey.split(".")[0] == "keywords"
            or fieldKey.split(".")[0] == "publicationDate"
            or fieldKey.split(".")[0] == "context"
        ):
            gen_key_list.append(i)

    meta = {
        "general": gen_key_list,
        "spatial": spatial_key_list,
        "temporal": temporal_key_list,
        "source": source_key_list,
        "license": license_key_list,
    }

    return meta

PeerRreviewContributorView#

View for the contributor role of the Open Peer Review process.

Bases: PeerReviewView

A view handling the contributor's side of the peer review process. This view supports rendering the review template and handling GET and POST requests for contributor's review.

Source code in dataedit/views.py
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
class PeerRreviewContributorView(PeerReviewView):
    """
    A view handling the contributor's side of the peer review process.
    This view supports rendering the review template and handling GET and
    POST requests for contributor's review.
    """

    def get(self, request, schema, table, review_id):
        """
        Handle GET requests for contributor's review. Loads necessary data and
        renders the contributor review template.

        Args:
            request (HttpRequest): The incoming HTTP GET request.
            schema (str): The schema of the table.
            table (str): The name of the table.
            review_id (int): The ID of the review.

        Returns:
            HttpResponse: Rendered HTML response for contributor review.
        """
        can_add = False
        peer_review = PeerReview.objects.get(id=review_id)
        table_obj = Table.load(peer_review.schema, peer_review.table)
        if not request.user.is_anonymous:
            level = request.user.get_table_permission_level(table_obj)
            can_add = level >= login_models.WRITE_PERM
        oemetadata = self.load_json(schema, table, review_id)
        metadata = self.sort_in_category(schema, table, oemetadata=oemetadata)
        json_schema = self.load_json_schema()
        field_descriptions = self.get_all_field_descriptions(json_schema)
        review_data = peer_review.review.get("reviews", [])

        categories = [
            "general",
            "spatial",
            "temporal",
            "source",
            "license",
        ]
        state_dict = process_review_data(
            review_data=review_data, metadata=metadata, categories=categories
        )
        context_meta = {
            "config": json.dumps(
                {
                    "can_add": can_add,
                    "url_peer_review": reverse(
                        "dataedit:peer_review_contributor",
                        kwargs={
                            "schema": schema,
                            "table": table,
                            "review_id": review_id,
                        },
                    ),
                    "url_table": reverse(
                        "dataedit:view", kwargs={"schema": schema, "table": table}
                    ),
                    "topic": schema,
                    "table": table,
                }
            ),
            "table": table,
            "topic": schema,
            "meta": metadata,
            "json_schema": json_schema,
            "field_descriptions_json": json.dumps(field_descriptions),
            "state_dict": json.dumps(state_dict),
        }
        return render(request, "dataedit/opr_contributor.html", context=context_meta)

    def post(self, request, schema, table, review_id):
        """
        Handle POST requests for contributor's review. Merges and updates
        the review data in the PeerReview table.

        Missing parts:
            - merge contributor field review and reviewer field review

        Args:
            request (HttpRequest): The incoming HTTP POST request.
            schema (str): The schema of the table.
            table (str): The name of the table.
            review_id (int): The ID of the review.

        Returns:
            HttpResponse: Rendered HTML response for contributor review.

        Note:
            This method has some missing parts regarding the merging of contributor
            and reviewer field review.
        """

        context = {}
        if request.method == "POST":
            review_data = json.loads(request.body)
            review_post_type = review_data.get("reviewType")
            review_datamodel = review_data.get("reviewData")
            # unused
            # review_state = review_data.get("reviewFinished")
            current_opr = PeerReviewManager.filter_opr_by_id(opr_id=review_id)
            existing_reviews = current_opr.review
            merged_review = merge_field_reviews(
                current_json=existing_reviews, new_json=review_datamodel
            )

            current_opr.review = merged_review
            current_opr.update(review_type=review_post_type)

        return render(request, "dataedit/opr_contributor.html", context=context)

get(request, schema, table, review_id) #

Handle GET requests for contributor's review. Loads necessary data and renders the contributor review template.

Parameters:

Name Type Description Default
request HttpRequest

The incoming HTTP GET request.

required
schema str

The schema of the table.

required
table str

The name of the table.

required
review_id int

The ID of the review.

required

Returns:

Name Type Description
HttpResponse

Rendered HTML response for contributor review.

Source code in dataedit/views.py
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
def get(self, request, schema, table, review_id):
    """
    Handle GET requests for contributor's review. Loads necessary data and
    renders the contributor review template.

    Args:
        request (HttpRequest): The incoming HTTP GET request.
        schema (str): The schema of the table.
        table (str): The name of the table.
        review_id (int): The ID of the review.

    Returns:
        HttpResponse: Rendered HTML response for contributor review.
    """
    can_add = False
    peer_review = PeerReview.objects.get(id=review_id)
    table_obj = Table.load(peer_review.schema, peer_review.table)
    if not request.user.is_anonymous:
        level = request.user.get_table_permission_level(table_obj)
        can_add = level >= login_models.WRITE_PERM
    oemetadata = self.load_json(schema, table, review_id)
    metadata = self.sort_in_category(schema, table, oemetadata=oemetadata)
    json_schema = self.load_json_schema()
    field_descriptions = self.get_all_field_descriptions(json_schema)
    review_data = peer_review.review.get("reviews", [])

    categories = [
        "general",
        "spatial",
        "temporal",
        "source",
        "license",
    ]
    state_dict = process_review_data(
        review_data=review_data, metadata=metadata, categories=categories
    )
    context_meta = {
        "config": json.dumps(
            {
                "can_add": can_add,
                "url_peer_review": reverse(
                    "dataedit:peer_review_contributor",
                    kwargs={
                        "schema": schema,
                        "table": table,
                        "review_id": review_id,
                    },
                ),
                "url_table": reverse(
                    "dataedit:view", kwargs={"schema": schema, "table": table}
                ),
                "topic": schema,
                "table": table,
            }
        ),
        "table": table,
        "topic": schema,
        "meta": metadata,
        "json_schema": json_schema,
        "field_descriptions_json": json.dumps(field_descriptions),
        "state_dict": json.dumps(state_dict),
    }
    return render(request, "dataedit/opr_contributor.html", context=context_meta)

post(request, schema, table, review_id) #

Handle POST requests for contributor's review. Merges and updates the review data in the PeerReview table.

Missing parts
  • merge contributor field review and reviewer field review

Parameters:

Name Type Description Default
request HttpRequest

The incoming HTTP POST request.

required
schema str

The schema of the table.

required
table str

The name of the table.

required
review_id int

The ID of the review.

required

Returns:

Name Type Description
HttpResponse

Rendered HTML response for contributor review.

Note

This method has some missing parts regarding the merging of contributor and reviewer field review.

Source code in dataedit/views.py
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
def post(self, request, schema, table, review_id):
    """
    Handle POST requests for contributor's review. Merges and updates
    the review data in the PeerReview table.

    Missing parts:
        - merge contributor field review and reviewer field review

    Args:
        request (HttpRequest): The incoming HTTP POST request.
        schema (str): The schema of the table.
        table (str): The name of the table.
        review_id (int): The ID of the review.

    Returns:
        HttpResponse: Rendered HTML response for contributor review.

    Note:
        This method has some missing parts regarding the merging of contributor
        and reviewer field review.
    """

    context = {}
    if request.method == "POST":
        review_data = json.loads(request.body)
        review_post_type = review_data.get("reviewType")
        review_datamodel = review_data.get("reviewData")
        # unused
        # review_state = review_data.get("reviewFinished")
        current_opr = PeerReviewManager.filter_opr_by_id(opr_id=review_id)
        existing_reviews = current_opr.review
        merged_review = merge_field_reviews(
            current_json=existing_reviews, new_json=review_datamodel
        )

        current_opr.review = merged_review
        current_opr.update(review_type=review_post_type)

    return render(request, "dataedit/opr_contributor.html", context=context)

Helper Functions#

Separated functionality that can be imported in other modules. It contains several functions that help with recurring tasks in the peer review system.

Provide helper functionality for views to reduce code lines in views.py make the codebase more modular.

get_readable_table_name(table_obj) #

get readable table name from metadata

Parameters:

Name Type Description Default
table_obj object

django orm

required

Returns:

Type Description
str

str

Source code in dataedit/helper.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def get_readable_table_name(table_obj: Table) -> str:
    """get readable table name from metadata

    Args:
        table_obj (object): django orm

    Returns:
        str
    """

    try:
        label = read_label(table_obj.name, table_obj.oemetadata)
    except Exception as e:
        raise e
    return label

get_review_for_key(key, review_data) #

Retrieve the review for a specific key from the review data.

Parameters:

Name Type Description Default
key str

The key for which to retrieve the review. review_data (dict): The review data containing reviews for various keys.

required

Returns:

Name Type Description
Any

The new value associated with the specified key in the review data, or None if the key is not found.

Source code in dataedit/helper.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
def get_review_for_key(key, review_data):
    """
    Retrieve the review for a specific key from the review data.

    Args:
        key (str): The key for which to retrieve the review.
            review_data (dict): The review data containing
            reviews for various keys.

    Returns:
        Any: The new value associated with the specified key
            in the review data, or None if the key is not found.
    """

    for review in review_data["reviewData"]["reviews"]:
        if review["key"] == key:
            return review["fieldReview"].get("newValue", None)
    return None

merge_field_reviews(current_json, new_json) #

Merge reviews from contributors and reviewers into a single JSON object.

Parameters:

Name Type Description Default
current_json dict

The current JSON object containing reviewer's reviews.

required
new_json dict

The new JSON object containing contributor's reviews.

required

Returns:

Name Type Description
dict

The merged JSON object containing both contributor's and reviewer's reviews.

Note

If the same key is present in both the contributor's and reviewer's reviews, the function will merge the field evaluations. Otherwise, it will create a new entry in the Review-Dict.

Source code in dataedit/helper.py
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
def merge_field_reviews(current_json, new_json):
    """
    Merge reviews from contributors and reviewers into a single JSON object.

    Args:
        current_json (dict): The current JSON object containing
            reviewer's reviews.
        new_json (dict): The new JSON object containing contributor's reviews.

    Returns:
        dict: The merged JSON object containing both contributor's and
            reviewer's reviews.

    Note:
        If the same key is present in both the contributor's and
            reviewer's reviews, the function will merge the field
            evaluations. Otherwise, it will create a new entry in
            the Review-Dict.
    """
    merged_json = new_json.copy()
    review_dict = {}

    for contrib_review in merged_json["reviews"]:
        category = contrib_review["category"]
        key = contrib_review["key"]
        review_dict[(category, key)] = contrib_review["fieldReview"]

    for reviewer_review in current_json["reviews"]:
        category = reviewer_review["category"]
        key = reviewer_review["key"]

        if (category, key) in review_dict:
            # Add field evaluations to the existing entry
            existing_field_review = review_dict[(category, key)]
            if isinstance(existing_field_review, dict):
                existing_field_review = [existing_field_review]
            if isinstance(reviewer_review["fieldReview"], dict):
                reviewer_review["fieldReview"] = [reviewer_review["fieldReview"]]
            merged_field_review = existing_field_review + reviewer_review["fieldReview"]
            review_dict[(category, key)] = merged_field_review
        else:
            # Create new entry in Review-Dict
            review_dict[(category, key)] = reviewer_review["fieldReview"]

    # Insert updated field scores back into the JSON
    merged_json["reviews"] = [
        {"category": category, "key": key, "fieldReview": review_dict[(category, key)]}
        for category, key in review_dict
    ]

    return merged_json

process_review_data(review_data, metadata, categories) #

Process the review data and update the metadata with the latest reviews and suggestions.

Parameters:

Name Type Description Default
review_data list

A list of dictionaries containing review data for each field.

required
metadata dict

The original metadata object that needs to be updated.

required
categories list

A list of categories in the metadata.

required

Returns:

Name Type Description
dict

A state dictionary containing the state of each field after processing the review data.

Note

The function sorts the fieldReview entries by timestamp (newest first) and updates the metadata with the latest reviewer suggestions, comments, and new values. The resulting state dictionary indicates the state of each field after processing.

Source code in dataedit/helper.py
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
def process_review_data(review_data, metadata, categories):
    """
    Process the review data and update the metadata with the latest reviews
    and suggestions.

    Args:
        review_data (list): A list of dictionaries containing review data for
                            each field.
        metadata (dict): The original metadata object that needs to be updated.
        categories (list): A list of categories in the metadata.

    Returns:
        dict: A state dictionary containing the state of each field
            after processing the review data.

    Note:
        The function sorts the fieldReview entries by timestamp (newest first)
        and updates the metadata with the latest reviewer suggestions,
        comments, and new values. The resulting state dictionary indicates
        the state of each field after processing.
    """
    state_dict = {}

    for review in review_data:
        field_key = review.get("key")
        field_review = review.get("fieldReview")

        if isinstance(field_review, list):
            # Sortiere die fieldReview-Einträge nach dem timestamp (neueste zuerst)
            sorted_field_review = sorted(
                field_review, key=lambda x: x.get("timestamp"), reverse=True
            )
            latest_field_review = (
                sorted_field_review[0] if sorted_field_review else None
            )

            if latest_field_review:
                state = latest_field_review.get("state")
                reviewer_suggestion = latest_field_review.get("reviewerSuggestion")
                reviewer_suggestion_comment = latest_field_review.get("comment")
                newValue = latest_field_review.get("newValue")
            else:
                state = None
                reviewer_suggestion = None
                reviewer_suggestion_comment = None
                newValue = None
        else:
            state = field_review.get("state")
            reviewer_suggestion = field_review.get("reviewerSuggestion")
            reviewer_suggestion_comment = field_review.get("comment")
            newValue = field_review.get("newValue")

        if reviewer_suggestion is not None and reviewer_suggestion_comment is not None:
            for category in categories:
                for item in metadata[category]:
                    if item["field"] == field_key:
                        item["reviewer_suggestion"] = reviewer_suggestion
                        item["suggestion_comment"] = reviewer_suggestion_comment
                        break

        if newValue is not None:
            for category in categories:
                for item in metadata[category]:
                    if item["field"] == field_key:
                        item["newValue"] = newValue
                        break

        state_dict[field_key] = state

    return state_dict

read_label(table, oemetadata) #

Extracts the readable name from @comment and appends the real name in parens. If comment is not a JSON-dictionary or does not contain a field 'Name' None is returned.

:param table: Name to append

:param comment: String containing a JSON-dictionary according to @Metadata

:return: Readable name appended by the true table name as string or None

Source code in dataedit/helper.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def read_label(table, oemetadata) -> str:
    """
    Extracts the readable name from @comment and appends the real name in parens.
    If comment is not a JSON-dictionary or does not contain a field 'Name' None
    is returned.

    :param table: Name to append

    :param comment: String containing a JSON-dictionary according to @Metadata

    :return: Readable name appended by the true table name as string or None
    """
    try:
        if oemetadata.get("title"):
            return oemetadata["title"].strip() + " (" + table + ")"
        elif oemetadata.get("Title"):
            return oemetadata["Title"].strip() + " (" + table + ")"

        else:
            return None

    except Exception:
        return None

recursive_update(metadata, review_data) #

Recursively update the metadata with new values from the review data.

Parameters:

Name Type Description Default
metadata dict

The original metadata dictionary to be updated.

required
review_data dict

The review data containing new values for various keys.

required
Note

The function traverses the review data, and for each key, it updates the corresponding value in the metadata if a new value is present and is not an empty string.

Source code in dataedit/helper.py
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def recursive_update(metadata, review_data):
    """
    Recursively update the metadata with new values from the review data.

    Args:
        metadata (dict): The original metadata dictionary to be updated.
        review_data (dict): The review data containing new values for various keys.

    Note:
        The function traverses the review data, and for each key, it updates the
        corresponding value in the metadata if a new value is present and is not
        an empty string.
    """

    for review_key in review_data["reviewData"]["reviews"]:
        keys = review_key["key"].split(".")

        if isinstance(review_key["fieldReview"], list):
            for field_review in review_key["fieldReview"]:
                new_value = field_review.get("newValue", None)
                if new_value is not None and new_value != "":
                    set_nested_value(metadata, keys, new_value)
        else:
            new_value = review_key["fieldReview"].get("newValue", None)
            if new_value is not None and new_value != "":
                set_nested_value(metadata, keys, new_value)

set_nested_value(metadata, keys, value) #

Set a nested value in a dictionary given a sequence of keys.

Parameters:

Name Type Description Default
metadata dict

The dictionary in which to set the value.

required
keys list

A list of keys representing the path to the nested value.

required
value Any

The value to set.

required
Note

The function navigates through the dictionary using the keys and sets the value at the position indicated by the last key in the list.

Source code in dataedit/helper.py
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
def set_nested_value(metadata, keys, value):
    """
    Set a nested value in a dictionary given a sequence of keys.

    Args:
        metadata (dict): The dictionary in which to set the value.
        keys (list): A list of keys representing the path to the nested value.
        value (Any): The value to set.

    Note:
        The function navigates through the dictionary using the keys and sets the value
        at the position indicated by the last key in the list.
    """

    for key in keys[:-1]:
        if key.isdigit():
            key = int(key)
        metadata = metadata[key]
    last_key = keys[-1]
    if last_key.isdigit():
        last_key = int(last_key)
    metadata[last_key] = value

Metadata Functions#

Provide functionality that is related to retrieving and updating the oemetadata resource from the database. The oemetadata is the object of a review.

Save Metadata to Database#

Save updated metadata for a specific table in the OEP database.

Parameters:

Name Type Description Default
schema str

The name of the OEP schema.

required
table str

The name of the table in the OEP schema.

required
updated_metadata dict

The updated metadata dictionary.

required
Note

This function loads the table object from the database, updates its metadata field, and then saves the updated table object back to the database.

Source code in dataedit/metadata/__init__.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
def save_metadata_to_db(schema, table, updated_metadata):
    """
    Save updated metadata for a specific table in the OEP database.

    Args:
        schema (str): The name of the OEP schema.
        table (str): The name of the table in the OEP schema.
        updated_metadata (dict): The updated metadata dictionary.

    Note:
        This function loads the table object from the database,
        updates its metadata field, and then saves the updated
        table object back to the database.
    """

    from dataedit.models import Table

    # Load the table object
    table_obj = Table.load(schema=schema, table=table)

    # Update the oemetadata field
    table_obj.oemetadata = updated_metadata

    # Save the updated table object
    table_obj.save()

Load Metadata from Database#

Load metadata for a specific table from the OEP database.

Parameters:

Name Type Description Default
schema str

The name of the OEP schema.

required
table str

The name of the table in the OEP schema.

required

Returns:

Name Type Description
dict

The loaded metadata dictionary.

Note

The function currently loads metadata from the Table.oemetadata field. There is a consideration to change this function to use a different approach or keep the old functionality (TODO).

Source code in dataedit/metadata/__init__.py
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
def load_metadata_from_db(schema, table):
    """
    Load metadata for a specific table from the OEP database.

    Args:
        schema (str): The name of the OEP schema.
        table (str): The name of the table in the OEP schema.

    Returns:
        dict: The loaded metadata dictionary.

    Note:
        The function currently loads metadata from the Table.oemetadata field.
        There is a consideration to change this function to use a different approach
        or keep the old functionality (TODO).
    """

    from dataedit.models import Table

    metadata = Table.load(schema=schema, table=table).oemetadata

    metadata = parse_meta_data(metadata, schema, table)
    return metadata

Models#

Django models.

PeerReview#

The model of the Open Peer Review defines what data is stored in the django database about each existing review. Next to the review itself it stores additional data about the the reviewer and contributor user and more. It is used in the PeerReviewManger.

Note

This model also provides functionality that is directly related to the model. It is up to discussion if we want to keep the functionality inside the model.

Bases: Model

Represents a peer review in the database.

Attributes:

Name Type Description
table CharField

Name of the table being reviewed.

schema CharField

Name of the schema where the table is located.

reviewer ForeignKey

The user who reviews.

contributor ForeignKey

The user who contributes.

is_finished BooleanField

Whether the review is finished.

date_started DateTimeField

When the review started.

date_submitted DateTimeField

When the review was submitted.

date_finished DateTimeField

When the review finished.

review JSONField

The review data in JSON format.

Source code in dataedit/models.py
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
class PeerReview(models.Model):
    """
    Represents a peer review in the database.

    Attributes:
        table (CharField): Name of the table being reviewed.
        schema (CharField): Name of the schema where the table is located.
        reviewer (ForeignKey): The user who reviews.
        contributor (ForeignKey): The user who contributes.
        is_finished (BooleanField): Whether the review is finished.
        date_started (DateTimeField): When the review started.
        date_submitted (DateTimeField): When the review was submitted.
        date_finished (DateTimeField): When the review finished.
        review (JSONField): The review data in JSON format.
    """

    table = CharField(max_length=1000, null=False)
    schema = CharField(max_length=1000, null=False)
    reviewer = ForeignKey(
        "login.myuser", on_delete=models.CASCADE, related_name="reviewed_by", null=True
    )
    contributor = ForeignKey(
        "login.myuser",
        on_delete=models.CASCADE,
        related_name="review_received",
        null=True,
    )
    is_finished = BooleanField(null=False, default=False)
    date_started = DateTimeField(max_length=1000, null=False, default=timezone.now)
    date_submitted = DateTimeField(max_length=1000, null=True, default=None)
    date_finished = DateTimeField(max_length=1000, null=True, default=None)
    review = JSONField(null=True)
    # TODO: Maybe oemetadata should be stored in a separate table and imported
    # via FK here / change also for Tables model
    oemetadata = JSONField(null=False, default=dict)

    # laden
    @classmethod
    def load(cls, schema, table):
        """
        Load the current reviewer user.
        The current review is review is determened by the latest date started.

        Args:
            schema (string): Schema name
            table (string): Table name

        Returns:
            opr (PeerReview): PeerReview object related to the latest
            date started.
        """
        opr = (
            PeerReview.objects.filter(table=table, schema=schema)
            .order_by("-date_started")
            .first()
        )
        return opr

    # TODO: CAUTION unfinished work ... fix: includes all id´s and not just the
    # related ones (reviews on same table) .. procedures false results
    def get_prev_and_next_reviews(self, schema, table):
        """
        Sets the prev_review and next_review fields based on the date_started
        field of the PeerReview objects associated with the same table.
        """
        # Get all the PeerReview objects associated with the same schema
        # and table name
        peer_reviews = PeerReview.objects.filter(table=table, schema=schema).order_by(
            "date_started"
        )

        current_index = None
        for index, review in enumerate(peer_reviews):
            if review.id == self.id:
                current_index = index
                break

        prev_review = None
        next_review = None

        if current_index is not None:
            if current_index > 0:
                prev_review = peer_reviews[current_index - 1]

            if current_index < len(peer_reviews) - 1:
                next_review = peer_reviews[current_index + 1]

        return prev_review, next_review

    def save(self, *args, **kwargs):
        review_type = kwargs.pop("review_type", None)
        pm_new = None

        if not self.contributor == self.reviewer:
            super().save(*args, **kwargs)
            # TODO: This causes errors if review list ist empty
            # prev_review, next_review = self.get_prev_and_next_reviews(
            #   self.schema, self.table
            # )

            # print(prev_review.id, next_review)
            # print(prev_review, next_review)
            # Create a new PeerReviewManager entry for this PeerReview
            # pm_new = PeerReviewManager(opr=self, prev_review=prev_review)

            if review_type == "save":
                pm_new = PeerReviewManager(
                    opr=self, status=ReviewDataStatus.SAVED.value
                )

            elif review_type == "submit":
                result = self.set_version_of_metadata_for_review(
                    schema=self.schema, table=self.table
                )
                if result[0]:
                    logging.info(result[1])
                elif result[0] is False:
                    logging.info(result[1])

                pm_new = PeerReviewManager(
                    opr=self, status=ReviewDataStatus.SUBMITTED.value
                )
                pm_new.set_next_reviewer()

            elif review_type == "finished":
                result = self.set_version_of_metadata_for_review(
                    schema=self.schema, table=self.table
                )
                if result[0]:
                    logging.info(result[1])
                elif result[0] is False:
                    logging.info(result[1])

                pm_new = PeerReviewManager(
                    opr=self, status=ReviewDataStatus.FINISHED.value
                )
                self.is_finished = True
                self.date_finished = timezone.now()
                super().save(*args, **kwargs)

            if pm_new:
                pm_new.save()

        else:
            raise ValidationError("Contributor and reviewer cannot be the same.")

    def update(self, *args, **kwargs):
        """
        Update the peer review if the latest peer review is not finished yet
        but either saved or submitted.

        """

        review_type = kwargs.pop("review_type", None)
        if not self.contributor == self.reviewer:
            current_pm = PeerReviewManager.load(opr=self)
            if review_type == "save":
                current_pm.status = ReviewDataStatus.SAVED.value
            elif review_type == "submit":
                current_pm.status = ReviewDataStatus.SUBMITTED.value
                current_pm.set_next_reviewer()
            elif review_type == "finished":
                self.is_finished = True
                self.date_finished = timezone.now()
                current_pm.status = ReviewDataStatus.FINISHED.value

            # update peere review manager related to this peer review entry
            current_pm.save()
            super().save(*args, **kwargs)
        else:
            raise ValidationError("Contributor and reviewer cannot be the same.")

    def set_version_of_metadata_for_review(self, table, schema, *args, **kwargs):
        """
        Once the peer review is started, we save the current version of the
        oemetadata that is present on the table to the peer review instance
        to be able to do the review to a fixed state of the metadata.

        A started review means a reviewer saves / submits or finishes (in case
        the review is completed in one go) a review.

        Args:
            table (str): Table name
            schema (str): Table database schema aka data topic

        Returns:
            State (tuple): Bool value that indicates weather there is already
            a version of oemetadata available for this review & readable
            status message.
        """
        table_oemetdata = Table.load(schema=schema, table=table).oemetadata

        if self.oemetadata is None:
            self.oemetadata = table_oemetdata
            super().save(*args, **kwargs)

            return (
                True,
                f"Set current version of table's: '{table}' oemetadata for review.",
            )

        return (
            False,
            f"This tables (name: {table}) review already got a version of oemetadata.",
        )

    def update_all_table_peer_reviews_after_table_moved(
        self, *args, to_schema, **kwargs
    ):
        # all_peer_reviews = self.objects.filter(table=table, schema=from_schema)
        # for peer_review in all_peer_reviews:
        if isinstance(self.review, str):
            review_data = json.loads(self.review)
        else:
            review_data = self.review

        review_data["topic"] = to_schema

        self.review = review_data
        self.schema = to_schema

        super().save(*args, **kwargs)

    @property
    def days_open(self):
        if self.date_started is None:
            return None  # Review has not started yet
        elif self.is_finished:
            return (self.date_finished - self.date_started).days  # Review has finished
        else:
            return (timezone.now() - self.date_started).days  # Review is still open

get_prev_and_next_reviews(schema, table) #

Sets the prev_review and next_review fields based on the date_started field of the PeerReview objects associated with the same table.

Source code in dataedit/models.py
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
def get_prev_and_next_reviews(self, schema, table):
    """
    Sets the prev_review and next_review fields based on the date_started
    field of the PeerReview objects associated with the same table.
    """
    # Get all the PeerReview objects associated with the same schema
    # and table name
    peer_reviews = PeerReview.objects.filter(table=table, schema=schema).order_by(
        "date_started"
    )

    current_index = None
    for index, review in enumerate(peer_reviews):
        if review.id == self.id:
            current_index = index
            break

    prev_review = None
    next_review = None

    if current_index is not None:
        if current_index > 0:
            prev_review = peer_reviews[current_index - 1]

        if current_index < len(peer_reviews) - 1:
            next_review = peer_reviews[current_index + 1]

    return prev_review, next_review

load(schema, table) classmethod #

Load the current reviewer user. The current review is review is determened by the latest date started.

Parameters:

Name Type Description Default
schema string

Schema name

required
table string

Table name

required

Returns:

Name Type Description
opr PeerReview

PeerReview object related to the latest

date started.

Source code in dataedit/models.py
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
@classmethod
def load(cls, schema, table):
    """
    Load the current reviewer user.
    The current review is review is determened by the latest date started.

    Args:
        schema (string): Schema name
        table (string): Table name

    Returns:
        opr (PeerReview): PeerReview object related to the latest
        date started.
    """
    opr = (
        PeerReview.objects.filter(table=table, schema=schema)
        .order_by("-date_started")
        .first()
    )
    return opr

set_version_of_metadata_for_review(table, schema, *args, **kwargs) #

Once the peer review is started, we save the current version of the oemetadata that is present on the table to the peer review instance to be able to do the review to a fixed state of the metadata.

A started review means a reviewer saves / submits or finishes (in case the review is completed in one go) a review.

Parameters:

Name Type Description Default
table str

Table name

required
schema str

Table database schema aka data topic

required

Returns:

Name Type Description
State tuple

Bool value that indicates weather there is already

a version of oemetadata available for this review & readable

status message.

Source code in dataedit/models.py
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
def set_version_of_metadata_for_review(self, table, schema, *args, **kwargs):
    """
    Once the peer review is started, we save the current version of the
    oemetadata that is present on the table to the peer review instance
    to be able to do the review to a fixed state of the metadata.

    A started review means a reviewer saves / submits or finishes (in case
    the review is completed in one go) a review.

    Args:
        table (str): Table name
        schema (str): Table database schema aka data topic

    Returns:
        State (tuple): Bool value that indicates weather there is already
        a version of oemetadata available for this review & readable
        status message.
    """
    table_oemetdata = Table.load(schema=schema, table=table).oemetadata

    if self.oemetadata is None:
        self.oemetadata = table_oemetdata
        super().save(*args, **kwargs)

        return (
            True,
            f"Set current version of table's: '{table}' oemetadata for review.",
        )

    return (
        False,
        f"This tables (name: {table}) review already got a version of oemetadata.",
    )

update(*args, **kwargs) #

Update the peer review if the latest peer review is not finished yet but either saved or submitted.

Source code in dataedit/models.py
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
def update(self, *args, **kwargs):
    """
    Update the peer review if the latest peer review is not finished yet
    but either saved or submitted.

    """

    review_type = kwargs.pop("review_type", None)
    if not self.contributor == self.reviewer:
        current_pm = PeerReviewManager.load(opr=self)
        if review_type == "save":
            current_pm.status = ReviewDataStatus.SAVED.value
        elif review_type == "submit":
            current_pm.status = ReviewDataStatus.SUBMITTED.value
            current_pm.set_next_reviewer()
        elif review_type == "finished":
            self.is_finished = True
            self.date_finished = timezone.now()
            current_pm.status = ReviewDataStatus.FINISHED.value

        # update peere review manager related to this peer review entry
        current_pm.save()
        super().save(*args, **kwargs)
    else:
        raise ValidationError("Contributor and reviewer cannot be the same.")

PeerReviewManager#

The Manager is introduced to be able to store additional information about the peer review process and separate it from the PeerReview model. The process is started by submitting a review and the manager maintains the order of which user has to take the next action to be able to hold and activate the process.

Note

This model also provides functionality that is directly related to the model. It is up to discussion if we want to keep the functionality inside the model.

Bases: Model

Manages peer review processes.

This model handles the 1:n relation between table and open peer reviews. It tracks the days open for the peer review and its state. It determines who is next in the process between reviewer and contributor. It provides information about the previous and next review. It offers several methods that provide generic filters for the peer reviews.

Attributes:

Name Type Description
opr ForeignKey

The associated peer review.

current_reviewer CharField

The current reviewer.

status CharField

The current status of the review.

is_open_since CharField

How long the review has been open.

prev_review ForeignKey

The previous review in the process.

next_review ForeignKey

The next review in the process.

Source code in dataedit/models.py
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
class PeerReviewManager(models.Model):
    """
    Manages peer review processes.

    This model handles the 1:n relation between table and open peer reviews.
    It tracks the days open for the peer review and its state.
    It determines who is next in the process between reviewer and contributor.
    It provides information about the previous and next review.
    It offers several methods that provide generic filters for the peer reviews.

    Attributes:
        opr (ForeignKey): The associated peer review.
        current_reviewer (CharField): The current reviewer.
        status (CharField): The current status of the review.
        is_open_since (CharField): How long the review has been open.
        prev_review (ForeignKey): The previous review in the process.
        next_review (ForeignKey): The next review in the process.
    """

    REVIEW_STATUS = [(status.value, status.name) for status in ReviewDataStatus]
    REVIEWER_CHOICES = [(choice.value, choice.name) for choice in Reviewer]

    opr = ForeignKey(
        PeerReview, on_delete=models.CASCADE, related_name="review_id", null=False
    )
    current_reviewer = models.CharField(
        choices=REVIEWER_CHOICES, max_length=20, default=Reviewer.REVIEWER.value
    )
    status = models.CharField(
        choices=REVIEW_STATUS, max_length=10, default=ReviewDataStatus.SAVED.value
    )
    is_open_since = models.CharField(null=True, max_length=10)
    prev_review = ForeignKey(
        PeerReview,
        on_delete=models.CASCADE,
        related_name="prev_review",
        null=True,
        default=None,
    )  # TODO: add logic
    next_review = ForeignKey(
        PeerReview,
        on_delete=models.CASCADE,
        related_name="next_review",
        null=True,
        default=None,
    )  # TODO: add logic

    @classmethod
    def load(cls, opr):
        """
        Load the peer review manager associated with the given peer review.

        Args:
            opr (PeerReview): The peer review.

        Returns:
            PeerReviewManager: The peer review manager.
        """
        peer_review_manager = PeerReviewManager.objects.get(opr=opr)
        return peer_review_manager

    def save(self, *args, **kwargs):
        """
        Override the save method to perform additional logic
        before saving the peer review manager.
        """
        # Set is_open_since field if it is None
        if self.is_open_since is None:
            # Get the associated PeerReview instance
            peer_review = self.opr

            # Set is_open_since based on the days_open property of the
            # PeerReview instance
            days_open = peer_review.days_open
            if days_open is not None:
                self.is_open_since = str(days_open)
        # print(self.is_open_since, self.status)
        # Call the parent class's save method to save the PeerReviewManager instance
        super().save(*args, **kwargs)

    @classmethod
    def update_open_since(cls, opr=None, *args, **kwargs):
        """
        Update the "is_open_since" field of the peer review manager.

        Args:
            opr (PeerReview): The peer review.
            If None, use the peer review associated with the manager.

        """
        if opr is not None:
            peer_review = PeerReviewManager.objects.get(opr=opr)
        else:
            peer_review = cls.opr

        days_open = peer_review.opr.days_open
        peer_review.is_open_since = str(days_open)

        # Call the parent class's save method to save the PeerReviewManager instance
        peer_review.save(*args, **kwargs)

    def set_next_reviewer(self):
        """
        Set the order on which peer will be required to perform a action to
        continue with the process.
        """
        # TODO:check for user identifies as ...
        if self.current_reviewer == Reviewer.REVIEWER.value:
            self.current_reviewer = Reviewer.CONTRIBUTOR.value
        else:
            self.current_reviewer = Reviewer.REVIEWER.value
        self.save()

    def whos_turn(self):
        """
        Get the user and role (contributor or reviewer) whose turn it is.

        Returns:
            Tuple[str, User]: The role and user.
        """
        role, result = None, None
        peer_review = self.opr
        if self.current_reviewer == Reviewer.REVIEWER.value:
            role = Reviewer.REVIEWER.value
            result = peer_review.reviewer
        else:
            role = Reviewer.CONTRIBUTOR.value
            result = peer_review.contributor

        return role, result

    @staticmethod
    def load_contributor(schema, table):
        """
        Get the contributor for the table a review is started.

        Args:
            schema (str): Schema name.
            table (str): Table name.

        Returns:
            User: The contributor user.
        """
        current_table = Table.load(schema=schema, table=table)
        try:
            table_holder = (
                current_table.userpermission_set.filter(table=current_table.id)
                .first()
                .holder
            )
        except AttributeError:
            table_holder = None
        return table_holder

    @staticmethod
    def load_reviewer(schema, table):
        """
            Get the reviewer for the table a review is started.
        .
            Args:
                schema (str): Schema name.
                table (str): Table name.

            Returns:
                User: The reviewer user.
        """
        current_review = PeerReview.load(schema=schema, table=table)
        if current_review and hasattr(current_review, "reviewer"):
            return current_review.reviewer
        else:
            return None

    @staticmethod
    def filter_opr_by_reviewer(reviewer_user):
        """
        Filter peer reviews by reviewer, excluding those with current peer
        is contributor and the data status "SAVED" in the peer review manager.

        Args:
            reviewer_user (User): The reviewer user.

        Returns:
            QuerySet: Filtered peer reviews.
        """
        return PeerReview.objects.filter(reviewer=reviewer_user).exclude(
            review_id__current_reviewer=Reviewer.CONTRIBUTOR.value,
            review_id__status=ReviewDataStatus.SAVED.value,
        )

    @staticmethod
    def filter_latest_open_opr_by_reviewer(reviewer_user):
        """
        Get the last open peer review for the given contributor.

        Args:
            contributor_user (User): The contributor user.

        Returns:
            PeerReview: Last open peer review or None if not found.
        """
        try:
            return (
                PeerReview.objects.filter(reviewer=reviewer_user, is_finished=False)
                .exclude(
                    review_id__current_reviewer=Reviewer.CONTRIBUTOR.value,
                    review_id__status=ReviewDataStatus.SAVED.value,
                )
                .latest("date_started")
            )
        except PeerReview.DoesNotExist:
            return None

    @staticmethod
    def filter_opr_by_contributor(contributor_user):
        """
        Filter peer reviews by contributor, excluding those with current peer
        is reviewer and the data status "SAVED" in the peer review manager.

        Args:
            contributor_user (User): The contributor user.

        Returns:
            QuerySet: Filtered peer reviews.
        """

        return PeerReview.objects.filter(contributor=contributor_user).exclude(
            review_id__current_reviewer=Reviewer.REVIEWER.value,
            review_id__status=ReviewDataStatus.SAVED.value,
        )

    @staticmethod
    def filter_latest_open_opr_by_contributor(contributor_user):
        """
        Get the last open peer review for the given contributor.

        Args:
            contributor_user (User): The contributor user.

        Returns:
            PeerReview: Last open peer review or None if not found.
        """
        try:
            return (
                PeerReview.objects.filter(
                    contributor=contributor_user, is_finished=False
                )
                .exclude(
                    review_id__current_reviewer=Reviewer.REVIEWER.value,
                    review_id__status=ReviewDataStatus.SAVED.value,
                )
                .latest("date_started")
            )
        except PeerReview.DoesNotExist:
            return None

    @staticmethod
    def filter_opr_by_table(schema, table):
        """
        Filter peer reviews by schema and table.

        Args:
            schema (str): Schema name.
            table (str): Table name.

        Returns:
            QuerySet: Filtered peer reviews.
        """
        return PeerReview.objects.filter(schema=schema, table=table)

    def filter_opr_by_id(opr_id):
        return PeerReview.objects.filter(id=opr_id).first()

filter_latest_open_opr_by_contributor(contributor_user) staticmethod #

Get the last open peer review for the given contributor.

Parameters:

Name Type Description Default
contributor_user User

The contributor user.

required

Returns:

Name Type Description
PeerReview

Last open peer review or None if not found.

Source code in dataedit/models.py
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
@staticmethod
def filter_latest_open_opr_by_contributor(contributor_user):
    """
    Get the last open peer review for the given contributor.

    Args:
        contributor_user (User): The contributor user.

    Returns:
        PeerReview: Last open peer review or None if not found.
    """
    try:
        return (
            PeerReview.objects.filter(
                contributor=contributor_user, is_finished=False
            )
            .exclude(
                review_id__current_reviewer=Reviewer.REVIEWER.value,
                review_id__status=ReviewDataStatus.SAVED.value,
            )
            .latest("date_started")
        )
    except PeerReview.DoesNotExist:
        return None

filter_latest_open_opr_by_reviewer(reviewer_user) staticmethod #

Get the last open peer review for the given contributor.

Parameters:

Name Type Description Default
contributor_user User

The contributor user.

required

Returns:

Name Type Description
PeerReview

Last open peer review or None if not found.

Source code in dataedit/models.py
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
@staticmethod
def filter_latest_open_opr_by_reviewer(reviewer_user):
    """
    Get the last open peer review for the given contributor.

    Args:
        contributor_user (User): The contributor user.

    Returns:
        PeerReview: Last open peer review or None if not found.
    """
    try:
        return (
            PeerReview.objects.filter(reviewer=reviewer_user, is_finished=False)
            .exclude(
                review_id__current_reviewer=Reviewer.CONTRIBUTOR.value,
                review_id__status=ReviewDataStatus.SAVED.value,
            )
            .latest("date_started")
        )
    except PeerReview.DoesNotExist:
        return None

filter_opr_by_contributor(contributor_user) staticmethod #

Filter peer reviews by contributor, excluding those with current peer is reviewer and the data status "SAVED" in the peer review manager.

Parameters:

Name Type Description Default
contributor_user User

The contributor user.

required

Returns:

Name Type Description
QuerySet

Filtered peer reviews.

Source code in dataedit/models.py
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
@staticmethod
def filter_opr_by_contributor(contributor_user):
    """
    Filter peer reviews by contributor, excluding those with current peer
    is reviewer and the data status "SAVED" in the peer review manager.

    Args:
        contributor_user (User): The contributor user.

    Returns:
        QuerySet: Filtered peer reviews.
    """

    return PeerReview.objects.filter(contributor=contributor_user).exclude(
        review_id__current_reviewer=Reviewer.REVIEWER.value,
        review_id__status=ReviewDataStatus.SAVED.value,
    )

filter_opr_by_reviewer(reviewer_user) staticmethod #

Filter peer reviews by reviewer, excluding those with current peer is contributor and the data status "SAVED" in the peer review manager.

Parameters:

Name Type Description Default
reviewer_user User

The reviewer user.

required

Returns:

Name Type Description
QuerySet

Filtered peer reviews.

Source code in dataedit/models.py
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
@staticmethod
def filter_opr_by_reviewer(reviewer_user):
    """
    Filter peer reviews by reviewer, excluding those with current peer
    is contributor and the data status "SAVED" in the peer review manager.

    Args:
        reviewer_user (User): The reviewer user.

    Returns:
        QuerySet: Filtered peer reviews.
    """
    return PeerReview.objects.filter(reviewer=reviewer_user).exclude(
        review_id__current_reviewer=Reviewer.CONTRIBUTOR.value,
        review_id__status=ReviewDataStatus.SAVED.value,
    )

filter_opr_by_table(schema, table) staticmethod #

Filter peer reviews by schema and table.

Parameters:

Name Type Description Default
schema str

Schema name.

required
table str

Table name.

required

Returns:

Name Type Description
QuerySet

Filtered peer reviews.

Source code in dataedit/models.py
708
709
710
711
712
713
714
715
716
717
718
719
720
@staticmethod
def filter_opr_by_table(schema, table):
    """
    Filter peer reviews by schema and table.

    Args:
        schema (str): Schema name.
        table (str): Table name.

    Returns:
        QuerySet: Filtered peer reviews.
    """
    return PeerReview.objects.filter(schema=schema, table=table)

load(opr) classmethod #

Load the peer review manager associated with the given peer review.

Parameters:

Name Type Description Default
opr PeerReview

The peer review.

required

Returns:

Name Type Description
PeerReviewManager

The peer review manager.

Source code in dataedit/models.py
500
501
502
503
504
505
506
507
508
509
510
511
512
@classmethod
def load(cls, opr):
    """
    Load the peer review manager associated with the given peer review.

    Args:
        opr (PeerReview): The peer review.

    Returns:
        PeerReviewManager: The peer review manager.
    """
    peer_review_manager = PeerReviewManager.objects.get(opr=opr)
    return peer_review_manager

load_contributor(schema, table) staticmethod #

Get the contributor for the table a review is started.

Parameters:

Name Type Description Default
schema str

Schema name.

required
table str

Table name.

required

Returns:

Name Type Description
User

The contributor user.

Source code in dataedit/models.py
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
@staticmethod
def load_contributor(schema, table):
    """
    Get the contributor for the table a review is started.

    Args:
        schema (str): Schema name.
        table (str): Table name.

    Returns:
        User: The contributor user.
    """
    current_table = Table.load(schema=schema, table=table)
    try:
        table_holder = (
            current_table.userpermission_set.filter(table=current_table.id)
            .first()
            .holder
        )
    except AttributeError:
        table_holder = None
    return table_holder

load_reviewer(schema, table) staticmethod #

Get the reviewer for the table a review is started.

. Args: schema (str): Schema name. table (str): Table name.

Returns:
    User: The reviewer user.
Source code in dataedit/models.py
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
@staticmethod
def load_reviewer(schema, table):
    """
        Get the reviewer for the table a review is started.
    .
        Args:
            schema (str): Schema name.
            table (str): Table name.

        Returns:
            User: The reviewer user.
    """
    current_review = PeerReview.load(schema=schema, table=table)
    if current_review and hasattr(current_review, "reviewer"):
        return current_review.reviewer
    else:
        return None

save(*args, **kwargs) #

Override the save method to perform additional logic before saving the peer review manager.

Source code in dataedit/models.py
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
def save(self, *args, **kwargs):
    """
    Override the save method to perform additional logic
    before saving the peer review manager.
    """
    # Set is_open_since field if it is None
    if self.is_open_since is None:
        # Get the associated PeerReview instance
        peer_review = self.opr

        # Set is_open_since based on the days_open property of the
        # PeerReview instance
        days_open = peer_review.days_open
        if days_open is not None:
            self.is_open_since = str(days_open)
    # print(self.is_open_since, self.status)
    # Call the parent class's save method to save the PeerReviewManager instance
    super().save(*args, **kwargs)

set_next_reviewer() #

Set the order on which peer will be required to perform a action to continue with the process.

Source code in dataedit/models.py
554
555
556
557
558
559
560
561
562
563
564
def set_next_reviewer(self):
    """
    Set the order on which peer will be required to perform a action to
    continue with the process.
    """
    # TODO:check for user identifies as ...
    if self.current_reviewer == Reviewer.REVIEWER.value:
        self.current_reviewer = Reviewer.CONTRIBUTOR.value
    else:
        self.current_reviewer = Reviewer.REVIEWER.value
    self.save()

update_open_since(opr=None, *args, **kwargs) classmethod #

Update the "is_open_since" field of the peer review manager.

Parameters:

Name Type Description Default
opr PeerReview

The peer review.

None
Source code in dataedit/models.py
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
@classmethod
def update_open_since(cls, opr=None, *args, **kwargs):
    """
    Update the "is_open_since" field of the peer review manager.

    Args:
        opr (PeerReview): The peer review.
        If None, use the peer review associated with the manager.

    """
    if opr is not None:
        peer_review = PeerReviewManager.objects.get(opr=opr)
    else:
        peer_review = cls.opr

    days_open = peer_review.opr.days_open
    peer_review.is_open_since = str(days_open)

    # Call the parent class's save method to save the PeerReviewManager instance
    peer_review.save(*args, **kwargs)

whos_turn() #

Get the user and role (contributor or reviewer) whose turn it is.

Returns:

Type Description

Tuple[str, User]: The role and user.

Source code in dataedit/models.py
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
def whos_turn(self):
    """
    Get the user and role (contributor or reviewer) whose turn it is.

    Returns:
        Tuple[str, User]: The role and user.
    """
    role, result = None, None
    peer_review = self.opr
    if self.current_reviewer == Reviewer.REVIEWER.value:
        role = Reviewer.REVIEWER.value
        result = peer_review.reviewer
    else:
        role = Reviewer.CONTRIBUTOR.value
        result = peer_review.contributor

    return role, result