8000 Refactor the implementation of Keywords on forms #48 by tdruez · Pull Request #54 · aboutcode-org/dejacode · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Refactor the implementation of Keywords on forms #48 #54

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

Merged
merged 7 commits into from
May 14, 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
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ Release notes
Packages (excluding the version).
https://github.com/nexB/dejacode/issues/113

- Refactor the implementation of Keywords on forms to allow more flexibilty.
Existing Keywords are suggested for consistency but any values is now allowed.
https://github.com/nexB/dejacode/issues/48

### Version 5.0.1

- Improve the stability of the "Check for new Package versions" feature.
Expand Down
34 changes: 34 additions & 0 deletions component_catalog/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1029,3 +1029,37 @@ def get_queryset(self):
"child",
)
)


class KeywordSerializer(DataspacedSerializer):
class Meta:
model = ComponentKeyword
fields = (
"api_url",
"uuid",
"label",
"description",
)
extra_kwargs = {
"api_url": {
"view_name": "api_v2:componentkeyword-detail",
"lookup_field": "uuid",
},
}


class KeywordViewSet(CreateRetrieveUpdateListViewSet):
queryset = ComponentKeyword.objects.all()
serializer_class = KeywordSerializer
lookup_field = "uuid"
search_fields = (
"label",
"description",
)
search_fields_autocomplete = ("label",)
ordering_fields = (
"label",
"created_date",
"last_modified_date",
)
allow_reference_access = True
47 changes: 23 additions & 24 deletions component_catalog/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from dje.forms import DataspacedModelForm
from dje.forms import DefaultOnAdditionLabelMixin
from dje.forms import Group
from dje.forms import JSONListChoiceField
from dje.forms import JSONListField
from dje.forms import OwnerChoiceField
from dje.forms import autocomplete_placeholder
from dje.mass_update import DejacodeMassUpdateForm
Expand All @@ -56,23 +56,29 @@
class SetKeywordsChoicesFormMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

keywords_field = self.fields.get("keywords")
if keywords_field:
if keywords_field := self.fields.get("keywords"):
keywords_qs = ComponentKeyword.objects.scope(self.dataspace)
labels = keywords_qs.values_list("label", flat=True)
keywords_field.choices = [(label, label) for label in labels]
keywords_field.widget.attrs.update(
{
"data-list": ", ".join(labels),
}
)
keywords_field.widget.attrs.update({"data-list": ", ".join(labels)})


class KeywordsField(JSONListField):
def __init__(self, **kwargs):
widget = AutocompleteInput(
attrs={
"data-api_url": reverse_lazy("api_v2:componentkeyword-list"),
},
display_link=False,
display_attribute="label",
)
kwargs.setdefault("widget", widget)
kwargs.setdefault("required", False)
super().__init__(**kwargs)


class ComponentForm(
LicenseExpressionFormMixin,
DefaultOnAdditionLabelMixin,
SetKeywordsChoicesFormMixin,
DataspacedModelForm,
):
default_on_addition_fields = ["configuration_status"]
Expand All @@ -83,10 +89,7 @@ class ComponentForm(
]
color_initial = True

keywords = JSONListChoiceField(
required=False,
widget=AwesompleteInputWidget(attrs=autocomplete_placeholder),
)
keywords = KeywordsField()

packages_ids = forms.CharField(
widget=forms.HiddenInput,
Expand Down Expand Up @@ -262,16 +265,12 @@ def clean(self):
class PackageForm(
LicenseExpressionFormMixin,
PackageFieldsValidationMixin,
SetKeywordsChoicesFormMixin,
DataspacedModelForm,
):
save_as = True
color_initial = True

keywords = JSONListChoiceField(
required=False,
widget=AwesompleteInputWidget(attrs=autocomplete_placeholder),
)
keywords = KeywordsField()

collect_data = forms.BooleanField(
required=False,
Expand Down Expand Up @@ -910,7 +909,7 @@ class ComponentAdminForm(
SetKeywordsChoicesFormMixin,
DataspacedAdminForm,
):
keywords = JSONListChoiceField(
keywords = JSONListField(
required=False,
widget=AdminAwesompleteInputWidget(attrs=autocomplete_placeholder),
)
Expand Down Expand Up @@ -944,7 +943,7 @@ class PackageAdminForm(
SetKeywordsChoicesFormMixin,
DataspacedAdminForm,
):
keywords = JSONListChoiceField(
keywords = JSONListField(
required=False,
widget=AdminAwesompleteInputWidget(attrs=autocomplete_placeholder),
)
Expand Down Expand Up @@ -990,7 +989,7 @@ class ComponentMassUpdateForm(
DejacodeMassUpdateForm,
):
raw_id_fields = ["owner"]
keywords = JSONListChoiceField(
keywords = JSONListField(
required=False,
widget=AwesompleteInputWidget(attrs=autocomplete_placeholder),
)
Expand Down Expand Up @@ -1063,7 +1062,7 @@ class Meta:
class PackageMassUpdateForm(
LicenseExpressionFormMixin, SetKeywordsChoicesFormMixin, DejacodeMassUpdateForm
):
keywords = JSONListChoiceField(
keywords = JSONListField(
required=False,
widget=AwesompleteInputWidget(attrs=autocomplete_placeholder),
)
Expand Down
17 changes: 14 additions & 3 deletions component_catalog/importers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# See https://github.com/nexB/dejacode for support or download.
# See https://aboutcode.org for more information about AboutCode FOSS projects.
#

import json
import os
from urllib.parse import urlparse
Expand All @@ -29,7 +30,7 @@
from component_catalog.models import Package
from component_catalog.models import Subcomponent
from component_catalog.programming_languages import PROGRAMMING_LANGUAGES
from dje.forms import JSONListChoiceField
from dje.forms import JSONListField
from dje.importers import BaseImporter
from dje.importers import BaseImportModelForm
from dje.importers import ComponentRelatedFieldImportMixin
Expand All @@ -40,6 +41,12 @@
from product_portfolio.models import ProductComponent
from product_portfolio.models import ProductPackage

keywords_help = (
get_help_text(Component, "keywords")
+ " You can add multiple keywords by separating them with commas, like this: "
"keyword1, keyword2."
)


class OwnerChoiceField(ModelChoiceFieldForImport):
def get_suggestions(self, value, limit=5):
Expand Down Expand Up @@ -157,8 +164,9 @@ class ComponentImportForm(
identifier_field="label",
)

keywords = JSONListChoiceField(
keywords = JSONListField(
required=False,
help_text=keywords_help,
)

acceptable_linkages = ImportMultipleChoiceField(
Expand All @@ -172,6 +180,7 @@ class Meta:
"children",
"packages",
"completion_level",
"request_count",
# JSONField not supported
"dependencies",
)
Expand Down Expand Up @@ -237,8 +246,9 @@ class PackageImportForm(
identifier_field="label",
)

keywords = JSONListChoiceField(
keywords = JSONListField(
required=False,
help_text=keywords_help,
)

class Meta:
Expand All @@ -248,6 +258,7 @@ class Meta:
# JSONField not supported
"dependencies",
"file_references",
"request_count",
"parties",
]

Expand Down
67 changes: 9 additions & 58 deletions component_catalog/tests/test_importers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import NON_FIELD_ERRORS
from django.forms import widgets
from django.test import TestCase
from django.urls import reverse

Expand All @@ -24,7 +23,6 @@
from component_catalog.importers import SubcomponentImporter
from component_catalog.models import AcceptableLinkage
from component_catalog.models import Component
from component_catalog.models import ComponentKeyword
from component_catalog.models import ComponentStatus
from component_catalog.models import ComponentType
from component_catalog.models import Subcomponent
Expand Down Expand Up @@ -639,49 +637,21 @@ def test_component_import_keywords(self):
formset_data["form-0-keywords"] = {"key": "value"}
importer = ComponentImporter(self.super_user, formset_data=formset_data)
self.assertFalse(importer.formset.is_valid())
expected = [{"keywords": ["Enter a list of values."]}]
expected = [{"keywords": ['Value must be valid JSON list: ["item1", "item2"].']}]
self.assertEqual(expected, importer.formset.errors)

formset_data["form-0-keywords"] = "keyword1"
importer = ComponentImporter(self.super_user, formset_data=formset_data)
self.assertFalse(importer.formset.is_valid())
expected = [
{"keywords": ["Select a valid choice. keyword1 is not one of the available choices."]}
]
self.assertEqual(expected, importer.formset.errors)

formset_data["form-0-keywords"] = "keyword1,keyword2"
importer = ComponentImporter(self.super_user, formset_data=formset_data)
self.assertFalse(importer.formset.is_valid())
expected = [
{"keywords": ["Select a valid choice. keyword1 is not one of the available choices."]}
]
self.assertEqual(expected, importer.formset.errors)

k1 = ComponentKeyword.objects.create(label="Keyword1", dataspace=self.dataspace)
k2 = ComponentKeyword.objects.create(label="Keyword2", dataspace=self.dataspace)

formset_data["form-0-keywords"] = f"{k1.label}"
importer = ComponentImporter(self.super_user, formset_data=formset_data)
self.assertTrue(importer.formset.is_valid())

formset_data["form-0-keywords"] = f" , {k1.label}, ,"
importer = ComponentImporter(self.super_user, formset_data=formset_data)
self.assertTrue(importer.formset.is_valid())

formset_data["form-0-keywords"] = f"{k1.label}, {k2.label}"
formset_data["form-0-keywords"] = ", keyword1,keyword2 ,"
importer = ComponentImporter(self.super_user, formset_data=formset_data)
self.assertTrue(importer.formset.is_valid())

importer.save_all()
self.assertEqual(1, len(importer.results["added"]))
added_component = importer.results["added"][0]
self.assertEqual(["Keyword1", "Keyword2"], added_component.keywords)

# Making sure the TextInput widget is used in place of the default Select
# as loading multiple initial values is problematic on the Select
keywords_field = importer.formset.forms[0].fields["keywords"]
self.assertIsInstance(keywords_field.widget, widgets.TextInput)
self.assertEqual(["keyword1", "keyword2"], added_component.keywords)

def test_component_import_license_expression(self):
formset_data = {
Expand Down Expand Up @@ -1171,49 +1141,29 @@ def test_package_import_keywords(self):
formset_data["form-0-keywords"] = {"key": "value"}
importer = PackageImporter(self.super_user, formset_data=formset_data)
self.assertFalse(importer.formset.is_valid())
expected = [{"keywords": ["Enter a list of values."]}]
expected = [{"keywords": ['Value must be valid JSON list: ["item1", "item2"].']}]
self.assertEqual(expected, importer.formset.errors)

formset_data["form-0-keywords"] = "keyword1"
importer = PackageImporter(self.super_user, formset_data=formset_data)
self.assertFalse(importer.formset.is_valid())
expected = [
{"keywords": ["Select a valid choice. keyword1 is not one of the available choices."]}
]
self.assertEqual(expected, importer.formset.errors)
self.assertTrue(importer.formset.is_valid())

formset_data["form-0-keywords"] = "keyword1,keyword2"
importer = PackageImporter(self.super_user, formset_data=formset_data)
self.assertFalse(importer.formset.is_valid())
expected = [
{"keywords": ["Select a valid choice. keyword1 is not one of the available choices."]}
]
self.assertEqual(expected, importer.formset.errors)

k1 = ComponentKeyword.objects.create(label="Keyword1", dataspace=self.dataspace)
k2 = ComponentKeyword.objects.create(label="Keyword2", dataspace=self.dataspace)

formset_data["form-0-keywords"] = f"{k1.label}"
importer = PackageImporter(self.super_user, formset_data=formset_data)
self.assertTrue(importer.formset.is_valid())

formset_data["form-0-keywords"] = f" , {k1.label}, ,"
formset_data["form-0-keywords"] = " , keyword1, ,"
importer = PackageImporter(self.super_user, formset_data=formset_data)
self.assertTrue(importer.formset.is_valid())

formset_data["form-0-keywords"] = f"{k1.label}, {k2.label}"
formset_data["form-0-keywords"] = "keyword1, keyword2"
importer = PackageImporter(self.super_user, formset_data=formset_data)
self.assertTrue(importer.formset.is_valid())

importer.save_all()
self.assertEqual(1, len(importer.results["added"]))
added_package = importer.results["added"][0]
self.assertEqual(["Keyword1", "Keyword2"], added_package.keywords)

# Making sure the TextInput widget is used in place of the default Select
# as loading multiple initial values is problematic on the Select
keywords_field = importer.formset.forms[0].fields["keywords"]
self.assertIsInstance(keywords_field.widget, widgets.TextInput)
self.assertEqual(["keyword1", "keyword2"], added_package.keywords)

def test_package_import_scancode_json_input_issues(self):
file = os.path.join(TESTFILES_LOCATION, "package_empty.json")
Expand Down Expand Up @@ -1257,6 +1207,7 @@ def test_package_import_scancode_json_proper_input(self):
def test_package_import_scancode_json_proper_no_summary(self):
file = os.path.join(TESTFILES_LOCATION, "package_from_scancode_no_summary.json")
importer = PackageImporter(self.super_user, file)
self.assertTrue(importer.is_valid())
self.assertEqual([], importer.fatal_errors)
self.assertTrue(importer.is_from_scancode)
importer.save_all()
Expand Down
5 changes: 2 additions & 3 deletions component_catalog/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4641,8 +4641,6 @@ def test_component_catalog_component_form_add(self):
self.assertIn(status, form.fields["configuration_status"].queryset)
self.assertNotIn(alternate_status, form.fields["configuration_status"].queryset)

self.assertEqual([(keyword.label, keyword.label)], form.fields["keywords"].choices)

# NameVersionValidationFormMixin
data = {
"name": self.component1.name,
Expand Down Expand Up @@ -4688,7 +4686,7 @@ def test_component_catalog_component_form_add(self):
"copyright": "Copyright",
"notice_text": "Notice",
"description": "Description",
"keywords": [keyword.label],
"keywords": [keyword.label, "Another keyword"],
"primary_language": "Python",
"homepage_url": "https://nexb.com",
"configuration_status": status.pk,
Expand All @@ -4701,6 +4699,7 @@ def test_component_catalog_component_form_add(self):
self.assertEqual(owner, component.owner)
self.assertEqual(status, component.configuration_status)
self.assertEqual(license1.key, component.license_expression)
self.assertEqual(["Key1", "Another keyword"], component.keywords)

def test_component_catalog_component_form_assigned_packages(self):
data = {
Expand Down
Loading
0