How to extend Wagtail search to include child InlinePanel Orderables? - search

I have a Wagtail site with a page of music conductors. Each conductor has a section with their name, photo and biography. The conductor details are stored inside child Orderable instances which the parent page is displaying using an InlinePanel.
When users search my site, I want the results to contain text coming from inside the conductor details (i.e. based on their name and full biography text in the Orderable).
Conductors Page
class ConductorsPage(Page):
max_count = 1
template = 'home/conductors.html'
content = RichTextField(
null=True,
blank=True,
)
search_fields = Page.search_fields + [
index.SearchField('content'),
index.SearchField('conductors'),
]
content_panels = Page.content_panels + [
FieldPanel('content', classname="full"),
MultiFieldPanel(
[
InlinePanel("conductors", label="Conductor")
],
heading="Conductors",
),
]
Conductor (Orderable)`
class Conductor(Orderable):
page = ParentalKey('home.ConductorsPage', related_name='conductors')
name = models.CharField(max_length=30)
photograph = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
biography = RichTextField(
null=True,
blank=True,
features=['bold', 'italic', 'hr', 'link'],
)
search_fields = Page.search_fields + [
index.SearchField('name'),
index.SearchField('biography'),
]
panels = [
FieldPanel("name"),
ImageChooserPanel("photograph"),
FieldPanel("biography"),
]
The above code brings results based on the parent content field but omits text from the Orderable (e.g, biography). I am using a Postgres database (wagtail.contrib.postgres_search.backend).
I've seen suggestions to create a method in the parent that concatenates text from the child fields and set a SearchField on that. But that seems like a clunky solution.
What is the recommended way of implementing this in the latest versions of Wagtail?

Thank you gasman - your comment was the solution I was looking for.
For anyone who is trying to implement this, Wagtail has a very elegant way of defining search fields for child entities: RelatedFields
The search fields are added on the parent page using RelatedFields. See code below for an example that worked for me. Now when a user runs a search the results include text within the Conductor's biography and/or name.
class ConductorsPage(Page):
max_count = 1
template = 'home/conductors.html'
content = RichTextField(
null=True,
blank=True,
features=['bold', 'italic', 'hr', 'link', 'image'],
)
search_fields = Page.search_fields + [
index.SearchField('content'),
index.RelatedFields('conductors', [
index.SearchField('name'),
index.SearchField('biography'),
]),
]
content_panels = Page.content_panels + [
FieldPanel('content', classname="full"),
FieldPanel('search_description', classname="full"),
MultiFieldPanel(
[
InlinePanel("conductors", label="Conductor")
],
heading="Conductors",
),
]
class Conductor(Orderable):
page = ParentalKey('home.ConductorsPage', related_name='conductors')
name = models.CharField(max_length=30)
photograph = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
biography = RichTextField(
null=True,
blank=True,
features=['bold', 'italic', 'hr', 'link'],
)
panels = [
FieldPanel("name"),
ImageChooserPanel("photograph"),
FieldPanel("biography"),
]

Related

Print the output in my desired format using nested serializers?

I am trying to send messages using django-rest framework.I have created serializers for both user and message.I am using django default user model for storing user's details.
I am pasting my model and serializers here:
class MessageModel(models.Model):
message = models.CharField(max_length=100)
created_at = models.DateTimeField(default=now)
updated_at = models.DateTimeField(default=now)
user = models.ForeignKey(User,on_delete=models.CASCADE,related_name='user')
class UserSerializer(ModelSerializer):
class Meta:
model = get_user_model()
fields = ['id','username','email']
class MessageSerializer(ModelSerializer):
created_by = UserSerializer(read_only=True)
class Meta:
model = MessageModel
fields = ['id','message', 'created_at', 'updated_at','user','created_by']
View :
class MessageViewSet(viewsets.ModelViewSet):
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
queryset = MessageModel.objects.all()
serializer_class = MessageSerializer
After creating message,I am getting response like below:
{
"id": 6,
"message": "Lorem ipsum",
"created_at": "2022-11-07T09:21:19.492219Z",
"updated_at": "2022-11-07T09:21:19.492237Z",
"user": 2
}
but my output should looks like in below format,everytime when a new message is created :
{
"id": 102,
"message": "Lorem ipsum",
"created_at": "created time in UTC",
"updated_at": "last updated time in UTC",
"created_by": {
"id": 3,
"username": "testuser",
"email": "test#mail.com",
}
}
In case of any error, return the error message.
The name of the field is user, not created_by, so:
class MessageSerializer(ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = MessageModel
fields = ['id', 'message', 'created_at', 'updated_at', 'user']
or you can specify user as source:
class MessageSerializer(ModelSerializer):
created_by = UserSerializer(read_only=True, source='user')
class Meta:
model = MessageModel
fields = ['id', 'message', 'created_at', 'updated_at', 'created_by']
Note: Models normally have no …Model suffix. Therefore it might be better to rename MessageModel to Message.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: The related_name=… parameter [Django-doc]
is the name of the relation in reverse, so from the User model to the MessageModel
model in this case. Therefore it (often) makes not much sense to name it the
same as the forward relation. You thus might want to consider renaming the user relation to messages.

Nested serializer representation for PrimaryKeyRelatedField

I want to add and remove products to an order by only using their ids, but I want the order representation to look like a nested serializer.
My Models:
class Product(models.Model):
title = models.CharField(max_length=200)
price = models.DecimalField(max_digits=1_000, decimal_places=2)
def __str__(self):
return self.title
class Order(models.Model):
date = models.DateField()
products = models.ManyToManyField(Product, blank=True, related_name='orders')
My Serializers:
class ProductSerializer(serializers.ModelSerializer):
price = serializers.DecimalField(max_digits=1_000,
decimal_places=2,
coerce_to_string=False)
class Meta:
model = Product
fields = ['id', 'title', 'price']
class OrderSerializer(serializers.ModelSerializer):
products = serializers.PrimaryKeyRelatedField(queryset=Product.objects.all(), many=True)
class Meta:
model = Order
fields = ['id', 'date', 'products']
I'm trying to get the below representation:
{
"id": 1,
"date": "2021-08-12",
"products": [
{
"id": 1,
"title": "Item 1",
"price": 19.99
},
{
"id": 3,
"title": "Item 3",
"price": 49.99
}
]
}
However, if I want to create the above order the json should look like this:
{
"date": "2021-08-12",
"products":[1, 3]
}
And if I want to add a product with id==2 to the above order it should look like this:
{
"id": 1,
"date": "2021-08-12",
"products":[1, 3, 2]
}
I've tried overriding the to_representation() method and adding a nested serializer there, but I have no idea how to go about it. Should it look something like this, or am I going in a completely wrong direction here?
def to_representation(self, instance):
data = super().to_representation(instance)
data['products'] = ProductSerializer(data=instance['products'])
return data
Ended up fixing it with the to_representation() method. I don't know if this is the best way to do it, but it works for now.
def to_representation(self, instance):
data = super().to_representation(instance)
products_list = []
for product_id in data['products']:
product = instance.products.get(pk=product_id)
products_list.append(
{
'id': product.id,
'title': product.title,
'price': product.price
}
)
data['products'] = products_list
return data
use Nested Serializer Relationships.
and your OrderSerializer serializer class will be like this:
class OrderSerializer(serializers.ModelSerializer):
products = ProductSerializer(many=True)
class Meta:
model = Order
fields = ['id', 'date', 'products']

How to Updating JSONField in Django?

I'll give coffee to the person who found the solution Help me please. Query object:The result I want is just to update the value of auth_token
My model:
class Student(models.Model):
name = models.CharField(max_length=100, null=True, blank=True)
age = models.IntegerField(null=True, blank=True)
configuration=models.JSONField(null=True)
def __str__(self):
return self.name
Query object:The result I want is just to update the value of auth_token
class Bank:
def example(self):
query = Student.objects.get(id=1)
el =query.configuration
encoded_hand = json.dumps(el)
jsonObject = json.loads(encoded_hand)
for value in jsonObject:
checkValue=value['name']
if checkValue == 'Auth_token':
backUrl= value['value']
backUrl = "token"
query.configuration = backUrl # This is not working properly. Bad idea
query.save()
print("Ene bol:{}".format(backUrl))
MyJson :
[
{
"name": "Auth_token",
"value": "sfsdf"
},
{
"name": "Refresh_token",
"value": "sdfsdfs" # How to update this value token ?
},
{
"name": "BaseUrl",
"value": "test"
}
]

DRF Serializer not displaying fields (Foreign Key and many2many) in default HTML form page API. Field is available in GET

DRF Serializer contains a group and inventory field which are many2many and foreign key. It is missing in default DRF HTML Form but available in GET view. currently, the depth field is enabled in Serializer. If i am removing depth then Foreign key is available in default HTML form, but still group many2many field is missing. I need both the fields for POST call or in DRF HTML Form.
Do i have to write some create method, but I do not want to create new record for Foreign key and many2many just want to utilize the existing field.
My Serializer class.
class MainHostSerializer(serializers.ModelSerializer):
class Meta:
model = MainHost
fields = (
'host_id',
'host_name',
'inventory',
'group'
)
# depth = 2
Raw view for default DRF HTML Form
{
"host_id": null,
"host_name": ""
}
Model Class
class MainHost(models.Model):
host_id = models.IntegerField(verbose_name='HOST ID', primary_key=True)
host_name = models.CharField(verbose_name='HOST NAME', max_length=512)
inventory = models.ForeignKey(related_name='inv_ins', on_delete=models.SET_NULL, to='hosts.MainInventory', blank=True, null=True)
group = models.ManyToManyField(MainGroup, related_name='hostgroups', through ='HostGroup')
Create Method for MainHost Serializer
def create(self, validated_data):
inv_data = validated_data.pop('inventory')
inv_res = MainInventory.objects.create(**inv_data)
group_data = validated_data.pop('group')
host_data = MainHost.objects.create(inventory = inv_res, **validated_data)
for g_data in group_data:
inv_data = g_data.pop('inv_id')
inv = MainInventory.objects.create(**inv_data)
group_res = MainGroup.objects.create(inv_id = inv, **g_data)
print(validated_data)
HostGroup.objects.create(host = host_data, group = group_res)
This was sample JSON
{
"count": 1692,
"next": "http://127.0.0.1:8000/api/mainhost/?page=2",
"previous": null,
"results": [
{
"host_id": 4087,
"host_name": "10.240.144.2",
"inventory": {
"inv_id": 91,
"inv_name": "GNS Switches (TestNet)",
"total_hosts": 539,
"total_groups": 1,
"org_name": "ABC_TestNet",
"description": "Inventory of ABC switches on Testnet",
"inv_variables": "environ: testnet"
},
"group": [
{
"group_id": 280,
"group_name": "aruba",
"total_hosts": 539,
"total_groups": 0,
"inv_id": {
"inv_id": 91,
"inv_name": "ABC Switches (TestNet)",
"total_hosts": 539,
"total_groups": 1,
"org_name": "ABC_TestNet",
"description": "Inventory of ABC switches on Testnet",
"inv_variables": "environ: testnet"
},
"description": "imported",
"group_variables": "{}",
"groupinv_name": "ABC Switches (TestNet)",
"groupinv_description": "",
"groupinv_source": "scm",
"groupinv_path": "TEC/GNS/Switches/testnet.ini"
}
],
"description": "imported",
"foreman_group": "[{'id': 280, 'name': 'aruba'}]",
"host_variables": "{}",
"ansible_facts": "{}"
}
]
}

flask-marshmallow custom fields

I use flask-marshmallow and mongoengine.
Also flask-restplus for my API server.
Here is my api.py
class BoardSchema(ma.Schema):
class Meta:
fields = ('no', 'title', 'body', 'tags', 'created_at', 'views')
board_schema = BoardSchema()
boards_schema = BoardSchema(many=True)
class ArticleList(Resource):
def get(self):
articles = Board.objects.all()
return boards_schema.jsonify(articles)
model.py
from datetime import datetime
from mongoengine import *
from config import DB_NAME
connect(DB_NAME)
class Board(Document):
d = datetime.now()
date = "{}-{}-{}".format(d.year, d.month, d.day)
no = SequenceField()
title = StringField(required=True)
body = StringField(required=True)
tags = ListField(StringField())
likes = ListField(StringField())
views = ListField(StringField())
password = StringField(required=True)
created_at = DateTimeField(default=date)
updated_at = DateTimeField(default=date)
When I access to /article, it's result like this ->
{
"body": "123",
"created_at": "2018-08-20T00:00:00+00:00",
"no": 1,
"tags": [
"MySQL",
"C"
],
"title": "\ud14c\uc2a4\ud2b8",
"views": [
"127.0.0.1"
]
}
in "views", ip will be added who read article.
But I want to count of all the list of views and include it to my result.
The result I wanted is here.
{
"body": "123",
"created_at": "2018-08-20T00:00:00+00:00",
"no": 1,
"tags": [
"MySQL",
"C"
],
"title": "\ud14c\uc2a4\ud2b8",
"views": 20
}
I'm new at flask-marshmallow so I'm so confused how can I solve this issue.
Thanks.
Maybe you can try like this:
class BoardSchemaCustom(ma.ModelSchema):
class Meta:
model = Board
views = ma.fields.method(deserialize="_custom_serializer")
def _custom_serializer(self, obj):
return len(obj.views)
Create instance of your custom schema:
custom_board_schema = BoardSchemaCustom()
and dump it:
dump, errors = custom_board_schema.schema.dump(Board.query.first())
>>> dump
i've got the same problem. and my code works after installing marshmallow-sqlalchemy
pip install marshmallow-sqlalchemy
see from offical documentation
https://flask-marshmallow.readthedocs.io/en/latest/
Below snippet would also work:
class BoardSchemaCustom(ma.ModelSchema):
class Meta:
model = Board
views = ma.fields.Function(lambda obj: len(obj.views))

Resources