Foreign key filter in a child object - sql

I have the models:
class Boss(models.Model):
fullname = models.TextField()
class Shop(models.Model):
name = models.TextField()
address = models.TextField()
phone = models.TextField()
boss = models.ForeignKey(
Boss, on_delete=models.CASCADE, related_name="shops"
)
class Employee(models.Model):
name = models.TextField()
phone = models.TextField()
shop = models.ForeignKey(
Shop, on_delete=models.CASCADE, related_name="employees"
)
class WorkSpace(models.Model):
name = models.TextField()
employee = models.ForeignKey(
Shop, on_delete=models.CASCADE, related_name="work_spaces"
)
Serializers:
class WorkSpaceSerializer(serializers.ModelSerializer):
class Meta:
model = WorkSpace
fields = ["id","type"]
class EmployeeSerializer(serializers.ModelSerializer):
work_spaces = WorkSpaceSerializer(many=True, read_only=True)
class Meta:
model = Employee
fields = ["id","work_spaces","name","phone"]
class ShopSerializer(serializers.ModelSerializer):
employees = EmployeeSerializer(many=True, read_only=True)
class Meta:
model = Shop
fields = ["id","employees","phone","name","address"]
class BossSerializer(serializers.ModelSerializer):
shops = ShopSerializer(many=True, read_only=True)
class Meta:
model = Boss
fields = ["id","fullname","shops"]
View
class BossListView(
ListModelMixin,
GenericViewSet,
):
queryset = Boss.objects.all()
serializer_class = BossSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
workspace_type = self.request.query_params.get("workspace_type")
qs = super().get_queryset()
if workspace_type:
qs = qs.filter(shops__employees__work_spaces__type=workspace_type).distinct()
return qs
I filtered with
Boss.objects.filter(shops__employees__work_spaces__type=C1)
and got:
{
"shops": [
{
"id": 32,
"name": "Garden flowers",
"address": "5 st. Hool-Maa",
"phone": "879124851861598",
"employees": [
{
"id": 150,
"name": "Mike",
"phone": "8154451246",
"work_spaces": [
{
"id": 497,
"type": "B12"
},
{
"id": 15,
"type": "Z5"
},
{
"id": 33,
"type": "C1"
}
]
}
]
}
]
}
But I only need C1 from work_spaces:
[{
"id": 33,
"type": "C1"
}]
How can I exclude other work_spaces from the queryset or do I need to convert the result to a list and then filter using a for loop? There can be many workspaces, and I don't need to show them all to the user, I need information about the Boss, the Shop, the Employee..

According to your view, you are applying the lower() method on the workspace_type.
So if you have workspace_type=C1, applying lower() will make workspace_type=c1, and your filter won't match any record.
I make here the assumption that your values have a capital letter.

Related

One to many query using marshmallow for response not working

I'm trying to make a query and the response as follows:
get_data = request.get_json()
email = get_data.get('email')
result = User.query.join(User_Preference).filter(User.email==email).first()
dump_data = developer_schema.dump(result)
return jsonify({'data' : dump_data})
from the following two tables defined as follows:
class User_Preference(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey(User.id))
preference = db.Column(db.String(45))
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(50), unique=True)
password = db.Column(db.String(256))
experience = db.Column(db.Integer)
avatar = db.Column(db.String(256))
revenue = db.Column(db.Integer)
preferences = relationship("User_Preference")
These are the marshmallow schemas I am using:
class DeveloperPreferences(ma.Schema):
class Meta:
fields = ('user_id', 'preference')
class DeveloperSchema(ma.Schema):
class Meta:
fields = ('id', 'email', 'avatar')
#model = User
preferences = ma.Nested(DeveloperPreferences, many = True)
However, the return I am getting is as follows:
{
"data": {
"avatar": "example.com",
"email": "example#test.com",
"id": 10
}
}
That is only the user part of the query is being returned not the preferences. Anyone have any idea on how to solve this?
In order to also display the preferences in the result, it is necessary to also specify the field in the fields to be displayed.
When defining the nested fields, you slipped into the meta specification with the indentation. However, the fields are set at the schema level.
class DeveloperPreferenceSchema(ma.Schema):
class Meta:
fields = ('user_id', 'preference')
class DeveloperSchema(ma.Schema):
class Meta:
fields = ('id', 'email', 'avatar', 'preferences')
preferences = ma.Nested(DeveloperPreferenceSchema, many=True)
The result should then be as follows.
{
"data": {
"avatar": "example.com",
"email": "example#test.com",
"id": 10,
"preferences": [
{
"preference": "anything",
"user_id": 10
},
{
"preference": "something",
"user_id": 10
}
]
}
}

DRF nested serializers custom output

Couldn't find problem like this, so here is my problem:
models.py (simplified)
class Root(models.Model):
name = models.CharField()
class TextFile(models.Model):
root = models.ForeignKey(Root)
name = models.CharField()
class ImageFile(models.Model):
root = models.ForeignKey(Root)
name = models.CharField()
serializers.py
class TextFileSerializer(serializers.Modelserializer):
class Meta:
model = TextFile
fields = '__all__'
class ImageFileSerializer(serializers.Modelserializer):
class Meta:
model = ImageFile
fields = '__all__'
class FilesSerializer(serializers.Modelserializer):
Texts = TextFileSerializer(source='textfile_set', many=True, read_only=True)
Images = ImageFileSerializer(source='imagefile_set', many=True, read_only=True)
class Meta:
model = Root
fields = '__all__'
class RootSerializer(serializers.Modelserializer):
files = FilesSerializer(many=True, read_only=True)
class Meta:
model = Root
fields = '__all__'
I've got only root 'id' and 'name' in output but no 'files'.
I expect output like this:
{
'id': 0,
'name': 'Root instance 1',
'files': {
'Texts': [
{'id': 0, 'name': 'Text1'},
{'id': 1, 'name': 'Text2'},
...
],
'Images': [
{'id': 0, 'name': 'Image1'},
{'id': 1, 'name': 'Image2'},
...
]
}
}
Can anyone help me with that?
Your FilesSerializer field in RootSerializer is always returning None because Root have no property named 'files'. DRF serializer don't add None fields in the response by default. Your FileSerializer ask for a RootModel.
What you want is a way to retrieve files from your root model, so what you can do is using a SerializerMethodField, and in this method, fetch the data you need, and serializer them using your FileSerializer (but as a normal serializer).
Like so :
class TextFileSerializer(serializers.ModelSerializer):
class Meta:
model = TextFile
fields = '__all__'
class ImageFileSerializer(serializers.ModelSerializer):
class Meta:
model = ImageFile
fields = '__all__'
class FilesSerializer(serializers.Serializer):
Texts = TextFileSerializer(source='textfile_set', many=True)
Images = ImageFileSerializer(source='imagefile_set', many=True)
class Meta:
fields = '__all__'
class RootSerializer(serializers.ModelSerializer):
files = serializers.SerializerMethodField()
class Meta:
model = Root
fields = '__all__'
def get_files(self, instance):
return FilesSerializer(instance).data
It returns
[
{
"id": 1,
"files": {
"Texts": [],
"Images": [
{
"id": 1,
"name": "testimage",
"root": 1
}
]
},
"name": ""
}
]

Flask marshmallow remove key from nested dict

I have the following schema:
class PublisherSchema(ma.SQLAlchemyAutoSchema):
class Meta:
fields = ('name',)
model = Publisher
class JournalSchema(ma.SQLAlchemyAutoSchema):
class Meta:
fields = ('title', 'publisher')
model = Journal
ordered = True
publisher = ma.Nested(PublisherSchema)
When I dump the JournalSchema I want the result to be:
{
"title": "hello",
"publisher: "bye"
}
But right now it dumps as:
{
"title": "hello",
"publisher": {
"name": "bye"
}
}
How can I nest the publisher value but not display the key?
Actually I figured it out. The way to do it is:
class JournalSchema(ma.SQLAlchemyAutoSchema):
publisher = fields.String(attribute="publisher.name")
class Meta:
fields = ('title', 'publisher')
model = Journal
ordered = True
This considers that 'publisher' is a relationship of the referenced model.

How to filter flask-marshmallow nested field?

I working on a user's public-facing profile page, where I would like to display the user's profile as well as their "published" recipes. I have the following UserSchema, but this schema displays all recipes including the one that has not been published. I want to strictly display only the published recipes. Is there a way to filter out recipes that have not been published? I looked through marshmallow documentation but could not find an answer.
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), nullable=False, unique=True)
email = db.Column(db.String(200), nullable=False, unique=True)
password = db.Column(db.String(200))
recipes = db.relationship('Recipe', backref='user')
class UserSchema(Schema):
class Meta:
ordered = True
id = fields.Int(dump_only=True)
username = fields.String(required=True)
email = fields.Email(required=True)
password = fields.Method(required=True)
recipes = fields.Nested("RecipeSchema", many=True, exclude=("author",))
Following is the RecipeModel and RecipeSchema,
class Recipe(db.Model):
__tablename__ = 'recipe'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
description = db.Column(db.String(200))
is_publish = db.Column(db.Boolean(), default=False)
user_id = db.Column(db.Integer(), db.ForeignKey("user.id"))
class RecipeSchema(Schema):
class Meta:
ordered = True
id = fields.Integer(dump_only=True)
name = fields.String(required=True, validate=[validate.Length(max=100)])
description = fields.String(validate=[validate.Length(max=200)])
is_publish = fields.Boolean(dump_only=True)
author = fields.Nested(UserSchema, attribute='user', dump_only=True, exclude=('email', ))
Resource responsible for returning user profile data is:
from schemas.user import UserSchema
user_schema = UserSchema()
class UserResource(Resource):
#classmethod
def get(cls, _id: int):
user = User.query.filter_by(id=_id).first()
if not user:
return {"message": gettext("user_not_found")}, 404
return user_schema.dump(user), 200
Current output is
{
"id": "1",
"username": "john.doe",
"recipes": [
{
"id": "1",
"name": "cheese pizza",
"description": "yummy",
"is_publish": true
},
{
"id": "2",
"name": "Potato Salad",
"description": "tags with sepearate function",
"is_publish": false
}
]
}
I want it to be
{
"id": "1",
"username": "john.doe",
"recipes": [
{
"id": "1",
"name": "cheese pizza",
"description": "yummy",
"is_publish": true
}
]
}

Serializing MPTT Tree with Django REST Framework

I searched for a similar question without success.. So, i am working on a website in which i am using django-mptt to organize categories. My Model looks like:
class Category(MPTTModel):
parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, related_name='children')
name = models.CharField(max_length=90)
slug = models.SlugField(unique=True)
_full_slug_separator = '/'
#property
def url(self):
names = [category.name for category in self.get_ancestors(include_self=True)]
return self._full_slug_separator.join(names)
I defined the CategorySerializer as bellow:
class CategorySerializer(serializers.ModelSerializer):
children = serializers.SerializerMethodField()
class Meta:
model = Category
fields = ('name', 'url', 'children')
def get_children(self, obj):
return CategorySerializer(obj.get_children(), many=True).data
# views.py
class CategoryList(generics.ListAPIView):
queryset = Category.objects.root_nodes()
serializer_class = CategorySerializer
The question is how can i:
1. have the 'url' data included in leaf nodes only.
2. have the 'children' data included in non leaf nodes only.
Here is an example of the output I am looking for
[
{
"title":"root node",
"children":[
{
"title":"leaf node",
"url":"link"
},
{
"title":"non leaf node",
"children":[
{
"title":"leaf node",
"url":"link"
}
]
},
{
"title":"non leaf node",
"children":[
{
"title":"non leaf node",
"children":[
{
"title":"leaf node",
"url":"link"
}
]
}
}
]
},
{
"title":"root node",
"url":"link"
}
]
Also i want to know if there is a good way for generating the 'url' to reduce queries
And thanks for any help in advance.
I ran into the same problem and found this question. But there were no answers. So, I am posting how I managed to do it for anyone who may need it in the future. This is most likely not the best solution but it works.
My models.py:
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from mptt.models import MPTTModel, TreeForeignKey
class Category(MPTTModel):
name = models.CharField(max_length=100, null=False, blank=False, verbose_name=_("category name"), help_text=_("format: required, max_length=100"))
slug = models.SlugField(max_length=150, null=False, blank=False, editable=False, verbose_name=_("category url"), help_text=_("format: required, letters, numbers, underscore or hyphen"))
parent = TreeForeignKey("self", on_delete=models.SET_NULL, related_name="children", null=True, blank=True, verbose_name=_("parent category"), help_text=_("format: not required"))
class MPTTMeta:
order_insertion_by = ['name']
class Meta:
verbose_name = _('product category')
verbose_name_plural = _('product categories')
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(Category, self).save(*args, **kwargs)
def __str__(self):
return self.name
My serializers.py:
from rest_framework import serializers
class SubCategorySerializer(serializers.ModelSerializer):
"""Serializer for lowest level category that has no children."""
parent = serializers.SerializerMethodField(source='get_parent')
class Meta:
model = Category
fields = ['id', 'name', 'slug', 'parent', ]
def get_parent(self, obj):
if obj.parent:
return obj.parent.name
class CategorySerializer(serializers.ModelSerializer):
"""Serializer for category."""
parent = serializers.SerializerMethodField(source='get_parent')
children = serializers.SerializerMethodField(source='get_children')
class Meta:
model = Category
fields = ['id', 'name', 'slug', 'parent', 'children', ]
def get_parent(self, obj):
if obj.parent:
return obj.parent.name
def get_children(self, obj):
if obj.children.exists():
children = [child for child in obj.children.all()]
children_with_children = [child for child in children if child.children.exists()]
children_without_children = [child for child in children if not child.children.exists()]
if children_with_children:
return CategorySerializer(children_with_children, many=True).data
if children_without_children:
return SubCategorySerializer(children_without_children, many=True).data
This way children is included in non-leaf nodes only.
Here is an example of how the data looks:
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"id": 4,
"name": "Clothes",
"slug": "clothes",
"parent": null,
"children": [
{
"id": 5,
"name": "Men's Clothes",
"slug": "mens-clothes",
"parent": "Clothes",
"children": [
{
"id": 7,
"name": "Men's Jeans",
"slug": "mens-jeans",
"parent": "Men's Clothes"
}
]
},
{
"id": 6,
"name": "Women's Clothes",
"slug": "womens-clothes",
"parent": "Clothes",
"children": [
{
"id": 8,
"name": "Women's Jeans",
"slug": "womens-jeans",
"parent": "Women's Clothes"
}
]
}
]
},
{
"id": 1,
"name": "Technology",
"slug": "technology",
"parent": null,
"children": [
{
"id": 3,
"name": "Laptop",
"slug": "laptop",
"parent": "Technology"
},
{
"id": 2,
"name": "Mobile Phone",
"slug": "mobile-phone",
"parent": "Technology"
}
]
}
]
}