-
Notifications
You must be signed in to change notification settings - Fork 5
feat: add AliasInlineSerializer #94
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
base: main
Are you sure you want to change the base?
Changes from all commits
ea25fe1
37ad54b
32217ee
300c83a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| from typing import Any | ||
|
|
||
| from djangocms_alias.models import AliasPlugin | ||
| from rest_framework import serializers | ||
|
|
||
| from djangocms_rest.serializers.plugins import GenericPluginSerializer, base_exclude | ||
Check noticeCode scanning / CodeQL Cyclic import Note
Import of module
djangocms_rest.serializers.plugins Error loading related location Loading
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @fsbraun can we ignore this? |
||
|
|
||
|
|
||
| class AliasInlineSerializer(GenericPluginSerializer): | ||
| content = serializers.SerializerMethodField() | ||
|
|
||
| class Meta: | ||
| model = AliasPlugin | ||
| exclude = tuple(base_exclude) | ||
|
|
||
| def get_content(self, instance: AliasPlugin) -> list[dict[str, Any]]: | ||
| request = self.request | ||
| language = getattr(instance, "language", None) | ||
| if not request or not language: # pragma: no cover | ||
| return [] | ||
|
|
||
| alias_stack = getattr(request, "_rest_alias_stack", None) | ||
| if alias_stack is None: | ||
| alias_stack = [] | ||
| request._rest_alias_stack = alias_stack | ||
|
|
||
| if instance.alias_id in alias_stack: | ||
| return [] | ||
|
|
||
| alias_stack.append(instance.alias_id) | ||
| try: | ||
| placeholder = instance.alias.get_placeholder( | ||
| language=language, | ||
| show_draft_content=bool(getattr(request, "_preview_mode", False)), | ||
| ) | ||
| if not placeholder: # pragma: no cover | ||
| return [] | ||
|
|
||
| from djangocms_rest.plugin_rendering import RESTRenderer | ||
Check noticeCode scanning / CodeQL Cyclic import Note
Import of module
djangocms_rest.plugin_rendering Error loading related location Loading
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @fsbraun exist to break circular dependencies |
||
|
|
||
| renderer = RESTRenderer(request=request) | ||
| return renderer.serialize_plugins( | ||
| placeholder=placeholder, | ||
| language=language, | ||
| context=self.context, | ||
| ) | ||
| finally: | ||
| alias_stack.pop() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,9 +45,7 @@ | |
| # Second choice: Use DRF naming conventions to build the default API URL for the related model | ||
| model_name = related_model._meta.model_name | ||
| try: | ||
| return get_absolute_frontend_url( | ||
| request, reverse(f"{model_name}-detail", args=(pk,)) | ||
| ) | ||
| return get_absolute_frontend_url(request, reverse(f"{model_name}-detail", args=(pk,))) | ||
| except NoReverseMatch: | ||
| pass | ||
|
|
||
|
|
@@ -132,26 +130,64 @@ | |
| request, | ||
| field.related_model, | ||
| getattr(instance, f"{field.name}_id"), | ||
| obj=( | ||
| getattr(instance, field.name) | ||
| if field.is_cached(instance) | ||
| else None | ||
| ), | ||
| obj=(getattr(instance, field.name) if field.is_cached(instance) else None), | ||
| ) | ||
| elif isinstance(field, JSON_FIELDS) and ret.get(field.name): | ||
| # If the field is a subclass of JSONField, serialize its value directly | ||
| ret[field.name] = serialize_soft_refs(request, ret[field.name]) | ||
| return ret | ||
|
|
||
|
|
||
| def get_plugin_serializer_overrides() -> dict[str, type]: | ||
| """Return built-in serializer overrides keyed by model label.""" | ||
| overrides: dict[str, type] = {} | ||
| if apps.is_installed("djangocms_alias"): # pragma: no cover | ||
| from djangocms_rest.serializers.alias import AliasInlineSerializer | ||
Check noticeCode scanning / CodeQL Cyclic import Note
Import of module
djangocms_rest.serializers.alias Error loading related location Loading
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @fsbraun exist to break circular dependencies |
||
|
|
||
| overrides["djangocms_alias.AliasPlugin"] = AliasInlineSerializer | ||
| return overrides | ||
|
|
||
|
|
||
| def resolve_plugin_serializer(plugin: Any, model_class: type[Model]) -> type | None: | ||
| """Resolve serializer class using plugin serializer first, then central fallback.""" | ||
| overrides = get_plugin_serializer_overrides() | ||
| model_label = f"{model_class._meta.app_label}.{model_class.__name__}" | ||
| return getattr(plugin, "serializer_class", None) or overrides.get(model_label) | ||
|
|
||
|
|
||
| def get_auto_model_serializer(model_class: type[Model]) -> type: | ||
| """ | ||
| Build (once) a generic ModelSerializer subclass that excludes | ||
| common CMS bookkeeping fields. | ||
| """ | ||
|
|
||
| opts = model_class._meta | ||
| real_fields = {field.name for field in opts.get_fields()} | ||
| exclude = tuple(base_exclude & real_fields) | ||
|
|
||
| meta_class = type( | ||
| "Meta", | ||
| (), | ||
| { | ||
| "model": model_class, | ||
| "exclude": exclude, | ||
| }, | ||
| ) | ||
| return type( | ||
| f"{model_class.__name__}AutoSerializer", | ||
| (GenericPluginSerializer,), | ||
| { | ||
| "Meta": meta_class, | ||
| }, | ||
| ) | ||
|
|
||
|
|
||
| class PluginDefinitionSerializer(serializers.Serializer): | ||
| """ | ||
| Serializer for plugin type definitions. | ||
| """ | ||
|
|
||
| plugin_type = serializers.CharField( | ||
| help_text="Unique identifier for the plugin type" | ||
| ) | ||
| plugin_type = serializers.CharField(help_text="Unique identifier for the plugin type") | ||
| title = serializers.CharField(help_text="Human readable name of the plugin") | ||
| type = serializers.CharField(help_text="Schema type") | ||
| properties = serializers.DictField(help_text="Property definitions") | ||
|
|
@@ -186,17 +222,8 @@ | |
| definitions = {} | ||
|
|
||
| for plugin in plugin_pool.plugins.values(): | ||
| # Use plugin's serializer_class or create a simple fallback | ||
| serializer_cls = getattr(plugin, "serializer_class", None) | ||
|
|
||
| if not serializer_cls: | ||
|
|
||
| class DynamicModelSerializer(serializers.ModelSerializer): | ||
| class Meta: | ||
| model = plugin.model | ||
| fields = "__all__" | ||
|
|
||
| serializer_cls = DynamicModelSerializer | ||
| serializer_cls = resolve_plugin_serializer(plugin, plugin.model) | ||
| serializer_cls = serializer_cls or get_auto_model_serializer(plugin.model) | ||
|
|
||
| try: | ||
| serializer_instance = serializer_cls() | ||
|
|
@@ -207,11 +234,7 @@ | |
| if field_name in base_exclude: | ||
| continue | ||
|
|
||
| properties[ | ||
| field_name | ||
| ] = PluginDefinitionSerializer.map_field_to_schema( | ||
| field, field_name | ||
| ) | ||
| properties[field_name] = PluginDefinitionSerializer.map_field_to_schema(field, field_name) | ||
|
|
||
| definitions[plugin.__name__] = { | ||
| "name": getattr(plugin, "name", plugin.__name__), | ||
|
|
@@ -270,9 +293,7 @@ | |
| # Extract nested properties | ||
| properties = {} | ||
| for nested_field_name, nested_field in field.fields.items(): | ||
| properties[ | ||
| nested_field_name | ||
| ] = PluginDefinitionSerializer.map_field_to_schema( | ||
| properties[nested_field_name] = PluginDefinitionSerializer.map_field_to_schema( | ||
| nested_field, nested_field_name | ||
| ) | ||
| if properties: | ||
|
|
||
Check notice
Code scanning / CodeQL
Cyclic import Note
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@fsbraun can we ignore this?