Skip to content

Latest commit

 

History

History
109 lines (91 loc) · 6.12 KB

File metadata and controls

109 lines (91 loc) · 6.12 KB

Branches

Name your branches in format {jira_issue_code}/{short_description}. Eg. TILA-74/api-specifications

Pull Request Process

  1. Name your pull request in format {jira_issue_code} | {description_of_the_pr}. Eg. TILA-74 | Api specifications
  2. Always use develop branch as base for feature branches. Code (develop branch) is merged to master only for before release.
  3. Always write a description for your pull request unless the title already describes the PR unambiguously.
  4. Select other active developers of the project as reviewers for your pull request. At least 1 approving review is required for a PR before merge.
  5. After PR has been approved and CI-pipeline has passed it can be merged to develop by anyone.

Serializers

To keep serializers consistent, we obey the following principles. These principles may change in future.

  • We want to easily determine if related field contains id or whole object. Always use _id at end of the field name if it is a foreign key id field.
  • If related models are exposed in their own API endpoint, use it to CRUD them. Use nested objects only if they are not used separately.
  • Check examples in api/examples.py for samples how to do different implementations using above rules.

Permissions

When implementing new API endpoints or new methods for existing ones permissions should always be checked and modified as needed. Permissions are implemented in tilavarauspalvelu as following.

Role models

  • Permissions are currently role based. There are currently general roles, service sector roles and unit roles.
  • Roles are dynamic and they can be modified or created without changing code. You can manage role choices and their permissions in Django Admin.
  • Roles can be assigned to users from Django Admin or through API.
  • Role models are located in permissions/models.py.

Helpers

  • To make code more readable helpers are implemented for checking if user has roles that has permissions for certain actions.
  • For example there is helper can_manage_service_sectors_applications that takes user and service sector as parameters and it then checks if user has roles required for that action.
  • New helpers should be created any time there is need for these kind of checks rather than directly accessing users roles.
  • Helpers are located in permissions/helpers.py.

API permissions

  • API permissions are permission classes that are assigned to each viewsets permission_classes attribute. They are inherited from rest frameworks BasePermission class.
  • API permission class should be implemented for each viewset. By default everything is restricted.
  • API permissions uses helpers to determine if user has permission for certain actions.
  • API permission classes can be found from permissions/api_permissions.py.

Translations

We use django-modeltranslation to deal with translations. Use TranslatedModelSerializer located in api/base.py to automatically register translated fields in API. Note: You still need to register the original field, such as fields = ["name"]. This will automatically register translated fields name_fi, name_en, name_sv, which will be nested in an object under the original field name as a key, by their respective language codes as field keys:

{
    "name": {
        "fi": "foo",
        "en": "bar",
        "sv": "baz",
    }
}

Testing

To guarantee that software is working as it supposed to, we obey the following testing principles. These principles are based on Helsinki Testing requirements.

  • By default, everything should be tested. We use pytest for testing.
  • Single unit test should only cover a single feature/function/method. When a test breaks, it should be as obvious as possible to detect where the problem lies.
  • Use clear and descriptive naming, such as test_authenticated_user_can_make_reservation or test_order_cannot_be_modified.
  • Readability is important. Avoid loops in tests.
  • Tests are located under their respective apps, for example tests for Space-models should be in spaces/tests.py. API-related tests are under api application, postfixed by related endpoint, such as api/test_reservation_api.py.
  • Abstract reusable test data in fixtures (conftest.py). Sometimes creating or manipulating objects during a test is necessary, but if the data could be used in another test, put it in fixtures.

Branches

Format your code with format.sh script to conform to style checks, and fix possible flake8 errors in your code.

OpenAPI documentation

To improve automatic OpenAPI schema introspection, we obey the following principles.

  • API endpoint groups, or tags in OpenAPI jargon, are given a description in settings.SPECTACULAR_SETTINGS['TAGS'].
  • ModelSerializer's model fields defined in Meta.fields are given a description using help_text via extra_kwargs.
    class Meta:
        extra_kwargs = {
            "field_name": {
                "help_text": "Field description.",
            }
        }
  • Non-model fields are given a description using help_text kwarg on the field.
    field_name = serializers.SerializerMethodField(help_text="Field description.")
  • Filters are given a description using help_text kwarg on the filter.
    filter_name = filters.NumberFilter(help_text="Filter description.")
  • ViewSets are given a description either by giving the class a docstring or by using the extend_schema decorator on the class.
    @extend_schema(description="ViewSet description.")
    extend_schema overrides docstring, if both are used.
  • View specific descriptions within a ViewSet can be given using the extend_schema_view decorator on the ViewSet class.
    @extend_schema_view(
        list=extend_schema(description="list description."),
        create=extend_schema(description="create description."),
    )
    View specific descriptions override ViewSet description.
  • SerializerMethodFields are given a type using type hinting.
    max_persons = serializers.SerializerMethodField()
    
    def get_max_persons(self, reservation_unit) -> int:
        return reservation_unit.get_max_persons()