Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add instance for model form #25

Merged
merged 2 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,15 @@ jobs:
run: poetry run coverage xml

- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
if: always()
with:
verbose: true
files: coverage.xml
flags: '${{ matrix.django-version }},${{ matrix.python-version }}'
name: codecov-umbrella
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}

gather_all:
if: ${{ always() }}
Expand Down
27 changes: 15 additions & 12 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
default_stages: [commit]
default_language_version:
node: "16.16.0"
python: "python3.9"

repos:
- repo: https://github.com/MarcoGorelli/absolufy-imports
rev: "v0.3.1"
hooks:
- id: absolufy-imports

- repo: https://github.com/ambv/black
rev: "22.6.0"
rev: "24.8.0"
hooks:
- id: black
language_version: python3.9
Expand All @@ -24,13 +22,13 @@ repos:
- toml

- repo: https://github.com/pycqa/isort
rev: 5.12.0
rev: 5.13.2
hooks:
- id: isort
args: ["-m=VERTICAL_HANGING_INDENT", "--combine-as", "--profile=black"]

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: "v4.3.0"
rev: "v4.6.0"
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
Expand All @@ -45,33 +43,38 @@ repos:
- "--no-ensure-ascii"

- repo: https://github.com/Lucas-C/pre-commit-hooks-safety
rev: "v1.3.0"
rev: "v1.3.3"
hooks:
- id: python-safety-dependencies-check
files: pyproject.toml
args: [--disable-audit-and-monitor, -i=42835, -i=42837, -i=42836]
args: [--disable-audit-and-monitor]

- repo: https://github.com/pre-commit/pygrep-hooks
rev: "v1.9.0"
rev: "v1.10.0"
hooks:
- id: python-no-log-warn
- id: python-check-mock-methods
- id: python-no-eval

- repo: https://github.com/PyCQA/bandit
rev: "1.7.4"
rev: "1.7.9"
hooks:
- id: bandit
files: "^admin_action_tools/.*"

- repo: https://github.com/codespell-project/codespell
rev: "v2.1.0"
rev: "v2.3.0"
hooks:
- id: codespell
args: ["-w"]
exclude: |
(?x)^(
admin_action_tools/tests/.*|
poetry.lock
)$

- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
rev: v9.0.0
rev: v9.18.0
hooks:
- id: commitlint
stages: [commit-msg]
Expand Down
65 changes: 65 additions & 0 deletions .safety-policy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Safety Security and License Configuration file
security:
ignore-vulnerabilities:
59293:
reason: we don't rely on it.
64976:
reason: we don't rely on it.
72110:
reason: we don't rely on it.
72109:
reason: we don't rely on it.
72111:
reason: we don't rely on it.
72095:
reason: we don't rely on it.
65771:
reason: we don't rely on it.
72519:
reason: we don't rely on it.
72520:
reason: we don't rely on it.
72515:
reason: we don't rely on it.
72521:
reason: we don't rely on it.
61586:
reason: we don't rely on it.
60956:
reason: we don't rely on it.
62126:
reason: we don't rely on it.
66742:
reason: fml, dev deps
64459:
reason: fml, dev deps
64396:
reason: fml, dev deps
67895:
reason: fml, dev deps
42837:
reason: fml, dev deps
42836:
reason: fml, dev deps
67136:
reason: fml, dev deps
61489:
reason: fml, dev deps
64436:
reason: fml, dev deps
64437:
reason: fml, dev deps
62156:
reason: fml, dev deps
58910:
reason: fml, dev deps
70716:
reason: fml, dev deps
70715:
reason: fml, dev deps
63073:
reason: fml, dev deps
58755:
reason: fml, dev deps
71064:
reason: fml, dev deps
2 changes: 1 addition & 1 deletion CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
identity and expression, level of experience, education, socioeconomic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.

Expand Down
2 changes: 1 addition & 1 deletion admin_action_tools/admin/confirm_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def _reconstruct_request_files():
log("Warning: no cached_object")
return

if type(cached_object) != self.model:
if not isinstance(cached_object, self.model):
# Do not use cache if the model doesn't match this model
log(f"Warning: cached_object is not of type {self.model}")
return
Expand Down
16 changes: 12 additions & 4 deletions admin_action_tools/admin/form_tool.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import functools
from importlib import import_module
from typing import Callable, Dict
from typing import Callable, Dict, Optional, Type

from django import forms
from django.contrib.admin import helpers
Expand Down Expand Up @@ -80,11 +80,11 @@ def run_form_tool(
if step == ToolAction.BACK:
# cancel ask, revert to previous form
data = tool_chain.rollback()
form_instance = form(data)
form_instance = self.get_instance(form, data=data, instance=queryset_or_object)
# First called by `Go` which would not have tool_name in params
elif step == ToolAction.CONFIRMED:
# form is filled
form_instance = form(request.POST)
form_instance = self.get_instance(form, data=request.POST, instance=queryset_or_object)
if form_instance.is_valid():
metadata = self.__get_metadata(form)
tool_chain.set_tool(tool_name, form_instance.data, metadata=metadata)
Expand All @@ -93,14 +93,22 @@ def run_form_tool(
# forward to next
return func(self, request, queryset_or_object)
else:
form_instance = form()
form_instance = self.get_instance(form, instance=queryset_or_object)

queryset: QuerySet = self.to_queryset(request, queryset_or_object)
context = self.build_context(request, func, queryset, form_instance, tool_name, display_queryset)

# Display form
return self.render_action_form(request, context)

@staticmethod
def get_instance(
form: Type[forms.BaseForm], data: Optional[dict] = None, instance: Optional[forms.BaseForm] = None
) -> forms.BaseForm:
if issubclass(form, forms.ModelForm): # pylint: disable=E721
return form(data, instance=instance)
return form(data)


def add_form_to_action(form: Form, display_queryset=True):
"""
Expand Down
1 change: 1 addition & 0 deletions admin_action_tools/file_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

from django.core.files.uploadedfile import InMemoryUploadedFile

try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Tests confirmation of add/change
on ModelAdmin that utilize caches
"""

import os
from importlib import reload

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Tests confirmation of action that uses django-object-action
"""

from importlib import reload

from selenium.webdriver.common.by import By
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Tests with different form input types
"""

from datetime import timedelta
from importlib import reload

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

Does not test confirmation of inline changes
"""

from importlib import reload

import pkg_resources
Expand Down Expand Up @@ -112,7 +113,7 @@ def test_should_respect_get_inlines(self):
# New in Django 3.0
django_version = pkg_resources.get_distribution("Django").parsed_version
if django_version.major < 3:
pytest.skip("get_inlines() introducted in Django 3.0, and is not in this version")
pytest.skip("get_inlines() introduced in Django 3.0, and is not in this version")

shoppingmall_admin.ShoppingMallAdmin.inlines = []
shoppingmall_admin.ShoppingMallAdmin.get_inlines = lambda self, request, obj=None: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
on ModelAdmin that utilize caches
and S3 as a storage backend
"""

import os
from importlib import reload

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Tests confirmation of action that uses django-object-action
"""

from importlib import reload

from selenium.webdriver.common.by import By
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Tests confirmation of action that uses django-object-action
"""

from importlib import reload

from selenium.webdriver.common.by import By
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
This is arguably the most we fiddle with the Django request
Thus we should test it extensively
"""

import time
from unittest import mock

Expand Down
28 changes: 28 additions & 0 deletions admin_action_tools/tests/unit/form-tool/test_form_action.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
from unittest import mock

from django import forms
from django.contrib.auth.models import User
from django.urls import reverse

from admin_action_tools.admin.form_tool import ActionFormMixin
from admin_action_tools.constants import CONFIRM_FORM
from admin_action_tools.tests.helpers import AdminConfirmTestCase
from tests.factories import InventoryFactory, ShopFactory
from tests.market.form import NoteActionForm
from tests.market.models import Shop

CONFIRM_FORM_UNIQUE = f"{CONFIRM_FORM}_{NoteActionForm.__name__}"

Expand Down Expand Up @@ -77,3 +82,26 @@ def test_form_action_with_not_valid_form(self):

self.assertIn("Configure the", response.rendered_content)
self.assertIn("This field is required.", response.rendered_content)

def test_get_instance_with_model_form(self) -> None:
form_data = mock.MagicMock(spec=dict)
model_instance = mock.MagicMock()

model_form_class = forms.modelform_factory(Shop, form=forms.ModelForm, fields="__all__")
form_instance = ActionFormMixin.get_instance(model_form_class, data=form_data, instance=model_instance)

self.assertIsInstance(form_instance, model_form_class)
self.assertEqual(form_instance.data, form_data)
self.assertEqual(form_instance.instance, model_instance)
self.assertIsInstance(form_instance, model_form_class)

def test_get_instance_with_form(self) -> None:
form_data = mock.MagicMock(spec=dict)

form_class = forms.Form
form_instance = ActionFormMixin.get_instance(form_class, data=form_data)

self.assertIsInstance(form_instance, form_class)
self.assertEqual(form_instance.data, form_data)
self.assertFalse(hasattr(form_instance, "instance"))
self.assertIsInstance(form_instance, form_class)
Loading
Loading