Skip to content

Commit

Permalink
fix: publish pipeline + doc updates
Browse files Browse the repository at this point in the history
  • Loading branch information
jhassine committed Nov 19, 2024
1 parent 778c564 commit 24aa79a
Show file tree
Hide file tree
Showing 18 changed files with 497 additions and 4,710 deletions.
4 changes: 3 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
// Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
"dockerfile": "../Dockerfile"
},
/*
"mounts": [
"source=/Users/jukkahassinen/dev/superschema/superschema,target=/workspaces/django-ninja-crudl/superschema,type=bind"
"source=/Users/jukkahassinen/dev/superschema/django2pydantic,target=/workspaces/django-ninja-crudl/django2pydantic,type=bind"
],
*/
"customizations": {
"vscode": {
"extensions": [
Expand Down
119 changes: 119 additions & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
name: CI/CD pipeline

on:
push:
branches:
- "*"

jobs:
tests:
name: Run tests and checks
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Build Docker image
run: |
docker build -t django-ninja-crudl .
- name: Run tests
run: |
docker run --volume $(pwd):/app --workdir /app --rm django-ninja-crudl nox -s noop
release:
# If main branch is updated, we want to release the package.
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs:
- tests
concurrency: release
runs-on: ubuntu-latest

permissions:
id-token: write
contents: write

continue-on-error: true

outputs:
released: ${{ steps.release.outputs.released }}

steps:
# Note: we need to checkout the repository at the workflow sha in case during the workflow
# the branch was updated. To keep PSR working with the configured release branches,
# we force a checkout of the desired release branch but at the workflow sha HEAD.
- name: Setup | Checkout Repository at workflow sha
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.sha }}

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"

- name: Install pypa/build
run: >-
python3 -m
pip install
build
--user
- name: Setup | Force correct release branch on workflow sha
run: |
git checkout -B ${{ github.ref_name }} ${{ github.sha }}
- name: Action | Semantic Version Release
id: release
# Adjust tag with desired version if applicable.
uses: python-semantic-release/python-semantic-release@v9.14.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
git_committer_name: "github-actions"
git_committer_email: "actions@users.noreply.github.com"
root_options: "--strict"

- name: Build | Build the distribution packages
run: >-
python3 -m build
- name: Store the distribution packages
uses: actions/upload-artifact@v4
with:
path: dist/
name: python-package-distributions

- name: Publish | Upload to GitHub Release Assets
uses: python-semantic-release/publish-action@v9.14.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ steps.release.outputs.tag }}


publish-to-testpypi:
name: Publish Python 🐍 distribution 📦 to TestPyPI
if: ${{ needs.release.outputs.released == 'true' }}
needs:
- release
runs-on: ubuntu-latest

environment:
name: testpypi
url: https://test.pypi.org/p/django-ninja-crudl

permissions:
id-token: write # IMPORTANT: mandatory for trusted publishing

steps:
- name: Download all the dists
uses: actions/download-artifact@v4
with:
name: python-package-distributions
path: dist/
- name: Publish distribution 📦 to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -446,8 +446,8 @@ $RECYCLE.BIN/

# End of https://www.toptal.com/developers/gitignore/api/macos,linux,windows,visualstudiocode,python,django,pycharm+all

.testmondata
.testmondata*
.import_linter_cache
lcov.info
debug.json
superschema
django2pydantic
15 changes: 9 additions & 6 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Visit https://bit.ly/cffinit to generate yours today!

cff-version: 1.2.0
title: superschema
title: django-ninja-crudl
message: >-
If you use this software, please cite it using the
metadata from this file.
Expand All @@ -11,11 +11,14 @@ authors:
- given-names: Jukka
family-names: Hassinen
email: jukka.hassinen@gmail.com
repository-code: 'https://github.com/jhassine/superschema'
abstract: Convert Django models to Pydantic models/schema
repository-code: 'https://github.com/NextGenContributions/django-ninja-crudl'
abstract: CRUDL REST API endpoints from Django ORM models using Django Ninja framework.
keywords:
- Django
- Pydantic
- model
- schema
- Ninja
- CRUD
- CRUDL
- REST
- API
- OpenAPI
license: MIT
60 changes: 60 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Contribution guidelines

Contributions are welcome! Here are some guidelines to help you get started.

## Support the project

If you like this project and want to support it, you can:

- Give it a star on GitHub.
- Share it with others.
- Report issues or request new features.
- Contribute code, documentation, or tests.
- Sponsor the project on GitHub.


## Reporting issues

If you find a bug or have a feature request, please open an issue. Make sure to include a detailed description of the issue or feature request, and include any relevant information that can help us reproduce the issue.


## Contributing code

### Setup development environment

If you are using Visual Studio Code, you can use the included devcontainer in order to quickly set up a proper development environment.

### Discuss your changes

If you are planning to make a significant change, it is a good idea to discuss it first with the project authors. You can open an issue to discuss your changes or discuss it with other contributors the project's discussion channels.

### Implement your changes

Make your changes in a new git branch. Make sure to add tests for your changes.

### Run the tests and checks

In order for your changes to be accepted, they must pass all the tests and checks. We are using `nox` to run the tests and checks.
The tests and checks are defined in the [noxfile.py](noxfile.py) file.

You can run `nox` locally to run the tests and checks:

```shell
nox
```

### Add yourself to the contributors list

If you want to get credit from your contribution, add yourself to the following files:

- [pyproject.toml](pyproject.toml)
- [CITATION.cff](CITATION.cff)

### Create a pull request

Once you are happy with your changes, create a pull request. Make sure to include a description of your changes and why they are needed.

### Review process

Your pull request will be reviewed by the project maintainers. They may ask for changes or suggest improvements. Once your pull request is approved, it will be merged into the main branch.

55 changes: 31 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
# Why this package?

This package aims to be the most simplest way to expose Django models securely via RESTful API CRUDL (Create, Retrieve, Update, Delete, List) endpoints and provide the most complete OpenAPI documentation for the exposed endpoints.
To provide the most simplest and quickest way to expose Django models securely as RESTful API CRUDL (Create, Retrieve, Update, Delete, List) endpoints and provide the most complete OpenAPI documentation for those endpoints.

# What is it?

The package provides a set of classes and methods to expose Django models via RESTful API CRUDL endpoints.

Behind the scenes, the package uses the Django Ninja, Django Ninja Extra and SuperSchema packages.
Behind the scenes, the package uses the [Django Ninja Extra](https://eadwincode.github.io/django-ninja-extra/) package which in turn uses [Django Ninja](https://django-ninja.dev/).
For the input and output validation schemas, [django2pydantic](https://github.com/NextGenContributions/django2pydantic) package is used.

# Tutorial on how to use

## Define the model along the fields exposed via the CRUDL endpoints
## Define the Django models along the fields exposed via the CRUDL endpoints

You can define you Django model fields that are exposed via the CRUDL endpoints in the model itself using the `CrudlApiMeta` nested class inside your model:

```python

from superschema import Infer, ModelFields
from django2pydantic import Infer, ModelFields
from django.db import models
from django_ninja_crudl.crudl import CrudlApiBaseMeta # <-- Add this import if you want type checking

Expand Down Expand Up @@ -50,13 +51,9 @@ class MyModelA(models.Model):

NOTE: In order to avoid accidentally exposing sensitive fields, you need to explicitly define the model fields that shall be exposed via the CRUDL endpoints. Some other libraries support exposing all fields (with optional exlude) which can lead to unintentional exposure of sensitive data.

NOTE: The `Infer` class from the `SuperSchema` library is used to infer the field type and details from the Django model fields.

NOTE: The library allows using different schemas for the `create`, `update`/`partial update`, `get one`, and `list` operations. For example, this can have the following practical advantages:
- Allows that some fields can be used during create but cannot be updated after the creation
- The list operation can expose only the fields that are needed for the list view
- The get one operation can expose more fields than the list operation
NOTE: The `Infer` class from the `django2pydantic` library is used to infer the field type and details from the Django model fields.

NOTE: The library allows using different schemas for the `create`, `update`/`partial update`, `get one`, and `list` operations. For example, this can have the following practical advantages: - Allows that some fields can be used during create but cannot be updated after the creation - The list operation can expose only the fields that are needed for the list view - The get one operation can expose more fields than the list operation

## Define the CRUDL endpoints for a model

Expand All @@ -75,7 +72,6 @@ class MyModelACrudl(Crudl):
model_class = MyModelA # <-- Add reference to your model class here that you defined previously
```


## Register the endpoint

Then you need to register the CRUDL controller in the API itself.
Expand All @@ -99,13 +95,14 @@ In order to control what is exposed and operable via the CRUDL endpoints, you ca
This serves as an additional layer of security and control.

You can define:
- the base queryset filter that applies to all CRUDL operations
- the operation type specific filters that apply to the create, update, delete, list, and get_one operations separately.

The filters are defined as Django model.Q objects. If you return an empty model.Q object, no additional filtering is applied.
- the base queryset filter that applies to all CRUDL operations
- the operation type specific filters that apply to the create, update, delete, list, and get_one operations separately.

The filters are defined as [Django models.Q objects](https://docs.djangoproject.com/en/5.1/topics/db/queries/#complex-lookups-with-q-objects). If you return an empty model.Q object, no additional filtering is applied.

For security reasons, the developer needs to explicitly define the filters for each operation type.
You need to explicitely override the following methods in the CRUDL controller. If you do not override them, the not implemented error will be raised.
You need to explicitely override the following methods in the CRUDL controller. If you do not override them, the "not implemented error" will be raised.

```python

Expand Down Expand Up @@ -150,9 +147,10 @@ The [`RequestDetails`](./django_ninja_crudl/types.py) object contains as much in
## Implement permission checks

The permission checks are for checking if the user has permission to perform the CRUDL operations for:
- the resource (=model) type
- the object (=instance)
- the related object (=related instance)

- the resource (=model) type
- the object (=instance)
- the related object (=related instance)

```python

Expand Down Expand Up @@ -199,7 +197,6 @@ class MyModelACrudl(Crudl):

```


## Implement the pre and post hooks (optional)

With the pre and post hooks, you can execute custom code before and after each CRUDL operation type.
Expand Down Expand Up @@ -286,14 +283,13 @@ class MyModelACrudl(Crudl):
## Summary of functionality

| Operation | Base queryset filter applied | Queryset filter | has_permission(...) | has_object_permission(...) | has_related_object_permission(...) | Model full_clean() method called | Pre and post hook methods |
|-------------------------|------------------------------|-----------------------------|---------------------|----------------------------|------------------------------------|----------------------------------|-------------------------------|
| ----------------------- | ---------------------------- | --------------------------- | ------------------- | -------------------------- | ---------------------------------- | -------------------------------- | ----------------------------- |
| Create | No | None | Yes | No | Yes | Yes | pre_create(), post_create() |
| Retrieve | Yes | get_filter_for_get_one(...) | Yes | Yes | Yes | Yes | pre_get_one(), post_get_one() |
| Update / Partial update | Yes | get_filter_for_update(...) | Yes | Yes | Yes | Yes | pre_update(), post_update() |
| Delete | Yes | get_filter_for_delete(...) | Yes | Yes | No | No | pre_delete(), post_delete() |
| List | Yes | get_filter_for_list(...) | Yes | No | No | No | pre_list(), post_list() |


## Create operation

The create operation is done through the Django model manager create method.
Expand All @@ -318,14 +314,25 @@ class MyModelA(models.Model):
objects = MyModelAManager()

```

## Validations

The framework provides the following validations:

### Request validation

The request payload structure is validated automatically using Pydantic. If the request payload does not match the expected structure, a 422 Unprocessable Entity response is returned.
The request payload structure is validated automatically using Pydantic just like it is done in Django Ninja. If the request payload does not match the expected structure, a 422 Unprocessable Entity response is returned.

### Create and update validation

The models are validated before creating or updating the object using the model's full_clean() method.
The models are validated before creating or updating the object using the Django model's [full_clean() method](https://docs.djangoproject.com/en/5.1/ref/models/instances/#validating-objects).

If you want to customize the validation, you can override or customize the full_clean method in the Django model.

# Ways to support this project

If you want to customize the validation, you can override or customize the full_clean method in the model.
- ⭐ Star this project on GitHub
- Share this project with your friends, colleagues, and on social media
- [Contribute code, documentation, and tests](CONTRIBUTING.md)
- Report bugs, issues, or feature requests
- Sponsor this project
2 changes: 2 additions & 0 deletions django_ninja_crudl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
WithObjectActions,
)

__version__ = "0.0.0"

__all__ = [
"Crudl",
"CrudlApiBaseMeta",
Expand Down
Loading

0 comments on commit 24aa79a

Please sign in to comment.