Flask-msearch in many to many relationship - elasticsearch

I'm trying to create a full text search using . It works well in one table. With this, my database has a many-to-many relationship:
from flask_msearch import Search
search = Search()
app = Flask(__name__)
tasks_topics= db.Table('tasks_topics',
db.Column('task_id', db.Integer, db.ForeignKey('tasks.id')),
db.Column('topics_id', db.Integer, db.ForeignKey('topics.id'))
)
class Tasks(db.Model):
__tablename__ = 'tasks'
__searchable__ = ['task','id','topics.name'] # these fields will be indexed by whoosh
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
url = db.Column(db.String(120))
task = db.Column(db.Text)
topics = db.relationship('Topics',
secondary=tasks_topics,
back_populates="tasks")
class Topics(db.Model):
__tablename__ = 'topics'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(140))
parent = db.Column(db.Integer, default=0)
# связи
tasks = db.relationship('Tasks',
secondary=tasks_topics,
back_populates="topics")
search.create_index(Tasks)
results = Tasks.query.msearch('Find x',fields=['task']).all()
print(results)
In documentation flask-msearch example shows one go many relationship. How should I solve this problem?

Related

Django GraphQL Mutation Updated, but no change in Database

I created an update mutation as follows, with django==3.1.4 and graphene==2.1.8 :
# models.py
class CustomUser(AbstractUser):
# email = models.EmailField()
firebase_id = models.CharField(max_length=50, null=True)
nickname = models.CharField(max_length=50, null=True)
name = models.CharField(max_length=20, null=True)
gender = models.IntegerField(choices=Gender, default=3)
phone = models.CharField(max_length=20, null=True)
birthday = models.DateField(default=datetime(2020,1,1))
address = models.CharField(max_length=200, null=True)
profile_image = models.ImageField(default='default-avatar.png', upload_to='users/',
null=True, blank=True)
class UpdateMember(graphene.Mutation):
class Arguments:
firebase_id = graphene.String(required=True)
nickname = graphene.String()
name = graphene.String()
gender = graphene.Int()
phone = graphene.String()
birthday = graphene.Date()
address = graphene.String()
profile_image = graphene.String()
class Meta:
exclude = ["password"]
member = graphene.Field(MemberType)
success = graphene.Boolean()
# #login_required
#staticmethod
def mutate(root, info, firebase_id, **kwargs):
success = False
member_instance = CustomUser.objects.get(firebase_id=firebase_id)
if member_instance:
print(member_instance)
success = True
for k, v in kwargs.items():
member_instance.k = v
member_instance.save()
return UpdateMember(member=member_instance, success=True)
else:
return UpdateMember(member=None, success=False)
Running GQL below:
mutation {
updateMember(
firebaseId:"777",
name:"JJJJ")
{
success
}
}
Response:
{
"data": {
"updateMember": {
"success": true
}
}
}
But I checked the database, it seems no change in it, I think .save() should have done the work persisting changes to database......
Creating Member works fine. Using PostgresQL
Could anyone figure out why?
There is several issues in your code:
You can not assign your model fields using string like that. See this thread
for k, v in kwargs.items():
member_instance.k = v
member_instance.save()
Currently your member_instance.k has nothing to do with variable k inside for loop.
firebase_id field should be unique.
Currently you call CustomUser.objects.get(firebase_id=firebase_id) which is risky because firebase_id is not unique field. This may lead Multiple objects error if you have more than one CustomUsers saved with same id. To fix it, just define:
class CustomUser(AbstractUser):
# email = models.EmailField()
firebase_id = models.CharField(max_length=50, unique=True)
...
To check if your member_instance has really updated. You can for example print out the values before saving it and run some test cases before final implementation. For example:
if member_instance:
print(member_instance)
success = True
for k, v in kwargs.items():
member_instance.k = v
print(member_instance.k)
print(k)
print(getattr(member_instance, k))
member_instance.save()

Graphene-Django create multiple instances

Let's say I've got a models.py with two tables:
class Category(models.Model):
cat = models.CharField(max_length=100)
class Thing(models.Model):
desc = models.CharField(max_length=100)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True)
and my schemas as follows:
class ThingType(DjangoObjectType):
class Meta:
model = Thing
class FindThing(graphene.ObjectType):
things = graphene.List(
ThingType,
search=graphene.String(),
thing=graphene.ID(),
)
def resolve_things(self, info, thing=None, search=None, **kwargs):
qs = Thing.objects.all()
if search:
filter = (
Q(desc__icontains=search)
)
qs = qs.filter(filter)
if thing:
qs = qs.filter(id=thing)
return qs
class CreateThing(graphene.Mutation):
id = graphene.Int()
desc = graphene.String()
category = graphene.Field(FindCategory)
class Arguments:
desc = graphene.String()
category = graphene.Int()
def mutate(self, info, desc, category):
thing = Thing(
desc=desc,
category=Category.objects.get(id=category)
)
thing.save()
return CreateThing(
id=thing.id,
desc=thing.desc,
category=thing.category_id
)
class CategoryType(DjangoObjectType):
class Meta:
model = Category
class GetCategory(graphene.ObjectType):
category = graphene.List(
CategoryType,
category=graphene.String(),
)
def resolve_category(self, info, category=None, **kwargs):
qs = Category.objects.all()
if category:
get = (
Q(category__contains=category)
)
qs = qs.get(get)
return qs
class FindCategory(graphene.ObjectType):
categories = graphene.List(
CategoryType,
search=graphene.String(),
cat=graphene.ID(),
)
def resolve_categories(self, info, cat=None, search=None, **kwargs):
qs = Category.objects.all()
if search:
filter = (
Q(cat__icontains=search)
)
qs = qs.filter(filter)
if cat:
qs = qs.filter(id=cat)
return qs
class CreateCategory(graphene.Mutation):
id = graphene.Int()
cat = graphene.String()
desc = graphene.String()
class Arguments:
cat = graphene.String()
desc = graphene.String()
def mutate(self, info, cat, desc):
category = Category(
cat=cat
)
category.save()
thing = Thing(
desc=desc,
category_id=category.id
)
thing.save()
return CreateCategory(
id=category.id,
cat=category.cat,
desc=thing.desc,
)
I've managed to create a schema where one can create a new category that already links to a newly created single thing:
mutation createCategory{
createCategory(cat:"cat7", desc:"defg"){
id
cat
desc
}
}
Is it possible to create a CreateCategory django-graphene schema where one can create a category with multiple additional new things?
You could allow the CreateCategory mutation to accept a list of descriptions for multiple things:
CreateCategory:
descs = graphene.List(String)
class Arguments:
descs = graphene.List(String)
and then loop over this list inside the mutate function:
new_things = []
for desc in descs:
thing = Thing(
desc=desc,
category_id=category.id
)
thing.save()
new_things.append(thing.desc)
return CreateCategory(
id=category.id,
cat=category.cat,
descs=new_things
)

Django REST update foreign key by id

Problem:
I want to update a foreign key field and I can not find my mistake why my changes to that fields are not saved. I want to be able to update clan and wingman.
serializer.py
class UpdateProfileInfoSerializer(serializers.Serializer):
id = serializers.CharField(required=True)
wingmanId = serializers.IntegerField(required=False)
clanId = serializers.IntegerField(required=False)
image = serializers.ImageField(required=False)
titleImage = serializers.ImageField(required=False)
description = serializers.CharField(required=False)
class Meta:
model = ProfileInfo
fields = ['id', 'clanId', 'description', 'image', 'titleImage', 'wingmanId']
def update(self, instance, validated_data):
clan_id = validated_data.get('clanId')
wingman_id = validated_data.get('wingmanId')
if clan_id:
if instance.clan:
instance.clan.id = clan_id
if wingman_id:
if instance.wingman:
instance.wingman.id = wingman_id
instance.image = validated_data.get('image', instance.image)
instance.titleImage = validated_data.get('titleImage', instance.titleImage)
instance.description = validated_data.get('description', instance.description)
instance.save()
return instance
Thank you for your help. I think I am doing something fundamentally wrong.
Try this please, I think it is better.
class UpdateProfileInfoSerializer(serializers.Serializer):
id = serializers.CharField(required=True)
wingmanId = serializers.IntegerField(required=False)
clanId = serializers.IntegerField(required=False)
image = serializers.ImageField(required=False)
titleImage = serializers.ImageField(required=False)
description = serializers.CharField(required=False)
class Meta:
model = ProfileInfo
fields = ['id', 'clanId', 'description', 'image', 'titleImage', 'wingmanId']
def update(self, instance, validated_data):
clan_id = validated_data.get('clanId')
wingman_id = validated_data.get('wingmanId')
if clan_id:
if instance.clan:
try:
clan_obj = Clan.objects.get(id=clan_id)
instance.clan = clan_obj
except:
pass
if wingman_id:
if instance.wingman:
try:
wingman_obj = Wingman.objects.get(id=wingman_id)
instance.wingman = wingman_obj
except:
pass
instance.image = validated_data.get('image', instance.image)
instance.titleImage = validated_data.get('titleImage', instance.titleImage)
instance.description = validated_data.get('description', instance.description)
instance.save()
return instance

Django Rest Framework - HyperlinkedRelatedField for abstract base class

My question is if is possible to add an HyperlinkedRelatedField in a serializer to get only the attributes of the base class. for example:
I want a json like that:
{
"modules": [
{
"moduleName": "M1: Fundamentos Técnicos",
"moduleDetails": "Bla bla bla.",
"moduleID": 0,
"userScore": 3,
"slides": [
{
"slideType": "Content",
"slideID": 0
},
{
"slideType": "Minigame1",
"slideID": 1
},
{
"slideType": "Video",
"slideID": 6
}
]
}
]
}
Here, Slide is the base class and Video and Minigame are subclasses.
Is possible this?, is possible add a HyperlinkedRelatedField for the url of each slide?.
Thanks in advance!
UPDATE!
this are my models:
class Module(TimeStampedModel):
moduleID = models.AutoField(primary_key=True)
moduleName = models.CharField(
max_length=100,
verbose_name='Nombre del modulo')
moduleDetails = models.TextField(verbose_name='Detalle')
moduleBullet1 = models.CharField(max_length=100, verbose_name='Punto 1')
moduleBullet2 = models.CharField(max_length=100, verbose_name='Punto 2')
moduleBullet3 = models.CharField(max_length=100, verbose_name='Punto 3')
moduleImageURL = models.ImageField(
upload_to="modulos", verbose_name='Imagen')
userScore = models.PositiveSmallIntegerField(
default=0, verbose_name='Score de usuario')
class Slide(TimeStampedModel):
CONTENT = 'Content'
MINIGAME = 'Minigame'
VIDEO = 'Video'
SLIDE_TYPE_CHOICES = (
(CONTENT, 'Contenido'),
(MINIGAME, 'Minigame'),
(VIDEO, 'Video'),
)
slideType = models.CharField(
max_length=20,
choices=SLIDE_TYPE_CHOICES,
default=CONTENT,
)
slideID = models.AutoField(primary_key=True)
slideOrder = models.PositiveSmallIntegerField(
verbose_name='Orden de visualizacion')
module = models.ForeignKey(Module, on_delete=models.CASCADE,
related_name="%(app_label)s_%(class)s_related",
related_query_name="%(app_label)s_%(class)ss",)
class Meta:
abstract = True
class VideoContent(Slide):
videoURL = models.URLField(verbose_name='URL de video')
class Minigame(Slide):
timeToFail=models.PositiveSmallIntegerField()
And in my serializers.py i want something like this:
class SlideSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Slide
fields = ('url', 'slideType', 'slideID')
class ModuleSerializer(serializers.ModelSerializer):
slides = SlideSerializer(many=True, read_only=True)
class Meta:
model = Module
fields = ('moduleID', 'moduleName', 'moduleDetails', 'moduleImageURL', 'userScore', 'slides')
My view.py
class ModuleViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
"""
queryset = Module.objects.all()
serializer_class = ModuleSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class SlideViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
"""
queryset = Slide.objects.all()
serializer_class = SlideSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
Slide.Objects raise an error, AttributeError: type object 'Slide' has no attribute 'objects'
You can't query abstract base classes. Slice is an abstract class, you cannot use queryset = Slide.objects.all() in SlideViewSet because an abstract model isn't an actual database object, and therefore cannot be queried.
You have to choose between abstract inheritance, in which case there is no database relationship between the two classes, or multi-table inheritance, which keeps the database relationship at a cost of efficiency (an extra database join) for each query.
Using multi-table inheritance
class Slide(TimeStampedModel):
CONTENT = 'Content'
MINIGAME = 'Minigame'
VIDEO = 'Video'
SLIDE_TYPE_CHOICES = (
(CONTENT, 'Contenido'),
(MINIGAME, 'Minigame'),
(VIDEO, 'Video'),
)
slideType = models.CharField(
max_length=20,
choices=SLIDE_TYPE_CHOICES,
default=CONTENT,
)
slideID = models.AutoField(primary_key=True)
slideOrder = models.PositiveSmallIntegerField(
verbose_name='Orden de visualizacion')
module = models.ForeignKey(Module, on_delete=models.CASCADE,
related_name="%(app_label)s_%(class)s_related",
related_query_name="%(app_label)s_%(class)ss",)
class VideoContent(Slide):
videoURL = models.URLField(verbose_name='URL de video')
class Minigame(Slide):
timeToFail=models.PositiveSmallIntegerField()
Now, your viewset:
class SlideViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
"""
queryset = Slide.objects.all()
serializer_class = SlideSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
Documentation on multi-table inheritance:
multi-table inheritance uses an implicit OneToOneField to link the
child and the parent, it’s possible to move from the parent down to
the child
Hope this clears the things up.

wagtail search in custom searchfields is not working

I am implementing a simple blog in wagtail. In the case of the blog pages the search should also look into the two custom fields 'intro' and 'body'. My BlogPage-model is as follows:
class PageWithSidebar(Page):
def get_context(self, request):
context = super(PageWithSidebar, self).get_context(request)
context['tags'] = BlogPageTag.objects.all().select_related().values('tag_id', 'tag_id__name').annotate(item_count=Count('tag_id')).order_by('-item_count')[:10]
context['categories'] = BlogCategory.objects.values('name').annotate(Count('name')).values('name').order_by('name')
context['recent_blogpages'] = Page.objects.filter(content_type__model='blogpage').filter(live='1').order_by('-first_published_at')
return context
class BlogPage(PageWithSidebar):
date = models.DateField("Post date")
intro = models.CharField(max_length=250)
body = RichTextField(blank=True)
tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
categories = ParentalManyToManyField('blog.BlogCategory', blank=True)
social_description = models.CharField(max_length=140, blank=True)
def main_image(self):
gallery_item = self.gallery_images.first()
if gallery_item:
return gallery_item.image
else:
return None
def main_image_caption(self):
gallery_item = self.gallery_images.first()
if gallery_item:
return gallery_item.caption
else:
return None
search_fields = PageWithSidebar.search_fields + [
index.SearchField('intro'),
index.SearchField('body'),
]
content_panels = PageWithSidebar.content_panels + [
MultiFieldPanel([
FieldPanel('date'),
FieldPanel('tags'),
FieldPanel('categories', widget=forms.CheckboxSelectMultiple),
FieldPanel('social_description'),
], heading="Blog information"),
FieldPanel('intro'),
FieldPanel('body'),
InlinePanel('gallery_images', label="Gallery images"),
]
The search works just fine for the 'title' field but not for the two custom fields. I just get no results if I search for words which are just contained in the 'intro' or 'body' field.
Any ideas what i am missing?
I didn't know that the default search backend doesn't support custom fields. After switching to elasticsearch the custom fields were included.

Resources