I am building an e-commerce website using Django, my models is like bellow :
class ProductAttribute(models.Model):
product=models.ForeignKey(Product,on_delete=models.CASCADE)
attributes_values = models.ManyToManyField(AttributeValue,verbose_name="Liste des attributs")
stock = models.PositiveIntegerField()
price = models.PositiveIntegerField(verbose_name="Prix")
image = models.ImageField(blank=True,null=True,upload_to="products")
class AttributeValue(models.Model):
attribute=models.ForeignKey(Attribute,on_delete=models.CASCADE,verbose_name="Attribut")
value = models.CharField(max_length=50,verbose_name="Valeur")
class Attribute(models.Model):
name = models.CharField(max_length=50,verbose_name="Nom")
my view.py
def getatts(request,product_id):
products_with_attributes=ProductAttribute.objects.filter(product__id=product_id)
res=#..... missing code to get attributes with values
return res
In the front end i want to retrieve attributes of a particular product to get them by order, to use them in select (ex:size,color choices) , for example if the query set of ProductAttribute is like:
[{id:1,product:1,attributes_values:[3,4],...},{id:1,product:1,attributes_values:[5,6],...}]
the result in JSON would be like so:
{
result:[
{
key: "color", // attribute.name
values: [
{id: 1, value: "Red",
choices:{
key:"size", // second attribute.name
values:[{id:3,value:"L"},{id:4,value:"XL"}]
}
},
{id: 2, value: "Black",
choices:{
key:"size",
values:[{id:5,value:"M"},{id:6,value:"XXL"}]
}
},
]
}
]
}
Note: I am using MYSQL as database
this is a dirty way of doing it and it is static way (max two attribute values) is there any way to do it using Django ORM:
products=ProductAttribute.objects.filter(product__id=id)
res={}
keys=[]
values=[]
for attribute_value in products.first().attributes_values.all():
keys.append({"id":attribute_value.attribute.id,"name":attribute_value.attribute.name})
res["id"]=keys[0]["id"]
res["name"]=keys[0]["name"]
# print(res)
for p in products:
attributes_values=p.attributes_values.all()
# print([ { "id":attv.id,"value":attv.value, "attribute_id":attv.attribute.id, "attribute_name":attv.attribute.name } for attv in attributes_values ])
for attv in attributes_values:
if attv.attribute.id==res["id"]:
exists=False
for v in values:
if v["id"]==attv.id:
exists=True
if not exists:
if len(keys)>1:
first_attribute={ "id":attv.id,"value":attv.value}
first_attribute["sub"]={"id":keys[1]["id"],"name":keys[1]["name"],"values":[]}
for pp in products:
for attv2 in pp.attributes_values.filter(productattribute__id__in= products.filter(attributes_values__id=attv.id).values("id")):
if attv2.attribute.id!=res["id"]:
exists2=False
for sub_value in first_attribute["sub"]["values"]:
if sub_value["id"]==attv2.id:
exists2=True
if not exists2:
first_attribute["sub"]["values"].append({"id":attv2.id,"value":attv2.value})
# first_attribute["sub"]["values"]
# p.attributes_values.all()[1]
values.append(first_attribute)
else:
values.append({ "id":attv.id,"value":attv.value,"sub":{}})
print(attv.attribute.id)
res["values"]=values
print(res)
Related
I'm working on a GET request with FastAPI to get data from database. I have to use ".join" on my query because I need to add filters on the child table. And I want the JSON response can include both the parent and child table. I used "relationship" in my model.py for "Parent" and "Child", and the following the schema.py works when I remove the .join(), and query on the Parent only. The response contains all the fields in Parent and Child. But I really need the filter on the Child table.
How should I build the schema to handle this joined tables scenario?
I appreciate for all the answers, thank you!!!
server.py
#app.get("/test/{id}", response_model = schema.TestRead)
def get_filter(id:int):
db = getDB('TESTDB')
result = db.query(Parent, Child)\
.join(Child)\
.filter(Parent.id == id)\
.filter(Child.name == 'TestName')\
.all()
return result
schema.py
class Parent(BaseModel):
id: int
class Config:
orm_mode = True
class Child(BaseModel):
id: int
name: str
class Config:
orm_mode = True
class TestRead(Parent):
child: List[Child]=[]
class Config:
orm_mode = True
I got all nulls..
{
"id": null,
"child": []
}
My expect response:
{
"id": 1
"child": {
"id": 1
"name": "TestName"
}
"child": {
"id": 1
"name": "TestName"
}
...
}
I am trying to create an endpoint where, having a User entity, I can add / remove existing Group entities to user.groups many-to-many field. But when I try to do it, django-rest-framework tries to create new group objects instead of finding existing ones.
I have defined two serializers where UserSerializer has a nested GroupSerializer:
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ['id', 'name']
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'username', 'email', 'groups']
groups = GroupSerializer(many=True)
def update(self, instance, validated_data):
data = validated_data.copy()
groups = data.pop('groups', [])
for key, val in data.items():
setattr(instance, key, val)
instance.groups.clear()
for group in groups:
instance.groups.add(group)
return instance
def create(self, validated_data):
data = validated_data.copy()
groups = data.pop('groups', [])
instance = self.Meta.model.objects.create(**data)
for group in groups:
instance.groups.add(group)
return instance
When I send a JSON through a PUT REST call (from django-rest-framework web interface):
{
"id": 6,
"username": "user5#example.com",
"email": "user5#example.com",
"groups": [
{
"id": 1,
"name": "AAA"
}
]
}
I expect serializer to find the Group with given id and add it to User. But instead, it tries to create a new user group and fails with duplicate key error:
{
"groups": [
{
"name": [
"group with this name already exists."
]
}
]
}
I searched over the internet and debugged myself and found no solution to this use case.
The create and update methods inside UserSerializerclass are never reached.
Edit: as asked, here are my views and urls:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all().order_by('-date_joined')
serializer_class = UserSerializer
class GroupViewSet(viewsets.ModelViewSet):
queryset = Group.objects.all()
serializer_class = GroupSerializer
Urls:
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)
urlpatterns = [
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
This seems to be the validation error due to a nested serializer model that contains unique constraint, see this post. According to the article, DRF did not handle this condition since it's hard to realize if the serializer is a nested serializer within another one. And that's why the create() and update() never been reached since the validation is done before calling them.
The way to work around this is to remove the uniqueness validator manually in GroupSerializer as follow:
class GroupSerializer(serializers.ModelSerializer):
class Meta:
model = Group
fields = ['id', 'name']
extra_kwargs = {
'name': {'validators': []},
}
BTW, there are some points that can be improved or should be corrected in your update() and create() code. Firstly, you didn't do instance.save() so the instance won't be update after the whole process done. Second, the groups are just a list of dictionary, and you should not add object that way. The following are the modification based on your OP code:
def update(self, instance, validated_data):
data = validated_data.copy()
groups = data.pop('groups', [])
for key, val in data.items():
setattr(instance, key, val)
instance.save() # This will indeed update DB values
group_ids = [g['id'] for g in groups]
instance.groups.clear()
instance.groups.add(*group_ids) # Add all groups once. Also you can replace these two lines with
# instance.groups.set(group_ids)
return instance
Though am late, here is how I did it, adding to Tsang-Yi Shen answer. However this worked for me because I was using django-role-permissions https://django-role-permissions.readthedocs.io/en/stable/setup.html
For the group serializer am only interested in the name.
from rolepermissions.roles import assign_role
class GroupModelSerializer(serializers.ModelSerializer):
name = serializers.SerializerMethodField()
class Meta:
model = Group
fields = ['name']
extra_kwargs = {
'name': {'validators': []},
}
def get_name(self, obj):
"""
This method modifies the way the name field is returned in a get request.
"""
return [group.name for group in obj.objects.all()]
In the UserSerializer, I modify the groups field to be returned as a list of groups a user belongs to, and received as a list instead of an OrderedDict in the JSON payload for creating a User, which can look something like this:
{
"email": "testuser#example.com",
"username": "testuser",
"name": "Test User",
"password": "password123",
"groups": ["doctor", "nurse", ]
}
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
groups = ListField(required=False, default=[], write_only=True)
user_groups = serializers.SerializerMethodField(read_only=True)
class Meta:
model = User
fields = ["id", "email", "username", "name", "password", "groups", "user_groups"]
depth = 2
def get_user_groups(self, obj):
"""
This method modifies the way the `groups` field is returned in a get request.
"""
return [group.name for group in obj.groups.all()]
def create(self, validated_data):
password = validated_data.pop('password')
gropus_to_add_user = validated_data.pop("groups")
user = User(**validated_data)
user.set_password(password)
user.save()
for group_name in groups_to_add_user:
# The assign_role function adds the user to a group.
# In this case group and roles may mean the same thing.
assign_role(user, group_name)
return user
You will have something like this when creating a user, here am using Swagger:
My issue is related to the django-rest-framework and is about how to group elements.
This is my serializers.py
from collaborativeAPP.models import *
from rest_framework import serializers
class VocabSerializer(serializers.ModelSerializer):
term_word = serializers.CharField(source='term.word',read_only=True)
kwdGroup = serializers.StringRelatedField()
class Meta:
model = Vocab
fields = ('id','term_word', 'meaning','kwdGroup')
class TermSerializer(serializers.ModelSerializer):
word = serializers.CharField(read_only=True)
class Meta:
model = Term
fields = ('url', 'word')
The following JSON it's the actual result:
{"results":[
{
"id": 5,
"term_word": "word1",
"meaning": "Text1"
"kwdGroup": "A"
},
{
"id": 6,
"term_word": "word2",
"meaning": "Text2"
"kwdGroup": "A"
},
{
"id": 7,
"term_word": "word3",
"meaning": "Text3"
"kwdGroup": "A"
}
]}
As you can notice kwdGroup is a repetitive element that I which to group.
I would like to group by kwdGroup:
{"A":[
{
"id": 5,
"term_word": "word1",
"meaning": "Text1"
},
{
"id": 6,
"term_word": "word2",
"meaning": "Text2"
},
{
"id": 7,
"term_word": "word3",
"meaning": "Text3"
}
]
}
I'm looking for answers on http://www.django-rest-framework.org/ on the API guide but I'm having difficulties finding an approach to lead with it.
Do you share this same issue? Do you have any suggestions on how can i do this? Do you have any example that deals with element grouping using django-rest-framework?
Thanks in advance.
Let's assume that the kwdGroup field is the relation field to a model called KeyWordGroup.
The default ListSerializer uses the to_representation method to render a list of the serialized objects rest_framework :
class ListSerializer(BaseSerializer):
...
def to_representation(self, data):
"""
List of object instances -> List of dicts of primitive datatypes.
"""
# Dealing with nested relationships, data can be a Manager,
# so, first get a queryset from the Manager if needed
iterable = data.all() if isinstance(data, models.Manager) else data
return [
self.child.to_representation(item) for item in iterable
]
We can modify the ListSerializer to group the results for example:
class VocabListSerializer(serializers.ListSerializer):
def to_representation(self, data):
iterable = data.all() if isinstance(data, models.Manager) else data
return {
kwdGroup: super().to_representation(Vocab.objects.filter(kwdGroup=kwdGroup))
for kwdGroup in KeyWordGroup.objects.all()
}
We can then use the modified VocabListSerializer with the VocabSerializer.
class VocabSerializer(serializers.Serializer):
...
class Meta:
list_serializer_class = VocabListSerializer
One way to achieve this is to use a SerializerMethodField. The below might be slightly different than your use case, but you can adopt accordingly. There are other ways of achieving this as well, including overwriting to_representation methods, but they rely on messing with the inner workings of DRF more than is relevant here.
models.py
class Dictionary(Model):
id = PrimaryKey
class Word(Model):
dictionary = ForeignKey(Dictionary, related_name='words')
word = Charfield()
group = Charfield()
serializers.py
class WordSerializer(serializers.ModelSerializer):
word = serializers.CharField(read_only=True)
class Meta:
model = Word
fields = ('word',)
class DictionarySerializer(serializers.ModelSerializer):
group_a = serializers.SerializerMethodField()
group_b = serializers.SerializerMethodField()
def get_group_a(self, instance):
return WordSerializer(instance.words.filter(group='a'), many=True).data
def get_group_b(self, instance):
return WordSerializer(instance.words.filter(group='b'), many=True).data
class Meta:
model = Dictionary
fields = ('group_a', 'group_b')
An example
>>> my_dictionary = Dictionary.objects.create()
>>> Word.objects.bulk_create(
Word(word='arrow', group='a' dictionary=my_dictionary),
Word(word='apple', group='a' dictionary=my_dictionary),
Word(word='baby', group='b' dictionary=my_dictionary),
Word(word='banana', group='b' dictionary=my_dictionary)
)
>>> serializer = DictionarySerializer(my_dictionary)
>>> print serializer.data
{
'group_a': {
'word': 'arrow',
'word': 'apple'
},
'group_b': {
'word': 'baby',
'word': 'banana'
},
}
I defined a nested model Product as follow. Each Product can belong to a lot of Productlist.
class Product(models.Model):
product_id = models.AutoField(primary_key=True)
product_name = models.CharField(max_length=50)
class Productlist(models.Model):
productlist_id = models.AutoField(primary_key=True)
productlist_name = models.CharField(max_length=50)
product = models.ManyToManyField(Product, related_name='productlists')
The corresponding serializers are:
class ProductlistSerializer(serializers.ModelSerializer):
class Meta:
model = Productlist
fields = ('productlist_id', 'productlist_name',)
class ProductSerializer(serializers.ModelSerializer):
productlists = ProductlistSerializer(many=True, required=False)
class Meta:
model = Product
fields = ('product_id', 'product_name', 'product lists')
def create(self, validated_data):
#some codes
When I POST a new Product (url(r'^api/products/$', views.ProductEnum.as_view()), I would like to update the product lists for adding the new product to the corresponding product lists. The JSON file I prefer to use is:
{
"product_name": "product1"
"productlist": [
{
"productlist_id": 1,
"productlist_name": "list1",
},
{
"productlist_id": 2,
"productlist_name": list2"
}
]
}
The problem is that I cannot get the productlist_id from validated_data. In Django Rest Framework, you always need to call to_internal_value() for deserializing data and generate validated_data. After some degugging, I checked the code of DRF and find the following snippets in to_internal_value():
def to_internal_value(self, data):
"""
Dict of native values <- Dict of primitive datatypes.
"""
if not isinstance(data, dict):
message = self.error_messages['invalid'].format(
datatype=type(data).__name__
)
raise ValidationError({
api_settings.NON_FIELD_ERRORS_KEY: [message]
})
ret = OrderedDict()
errors = OrderedDict()
fields = [
field for field in self.fields.values()
if (not field.read_only) or (field.default is not empty)
]
for field in fields:
validate_method = getattr(self, 'validate_' + field.field_name, None)
primitive_value = field.get_value(data)
try:
validated_value = field.run_validation(primitive_value)
if validate_method is not None:
validated_value = validate_method(validated_value)
except ValidationError as exc:
errors[field.field_name] = exc.detail
except DjangoValidationError as exc:
errors[field.field_name] = list(exc.messages)
except SkipField:
pass
else:
set_value(ret, field.source_attrs, validated_value)
if errors:
raise ValidationError(errors)
return ret
Please notice the to_internal_value's fields has ignored the IntegerField(read_only=True) for it cannot satisfy the following condition:
fields = [
field for field in self.fields.values()
if (not field.read_only) or (field.default is not empty)
]
So the validated_data will just have the following data:
{
"product_name": "product1"
"productlist": [
{
"productlist_name": "list1",
},
{
"productlist_name": list2"
}
]
}
How could I get the primary key of product list? Thanks in advance!
After some digging, I found that the read_only fields are only for output presentation. You can find the similar question on the offcial github link of Django REST Framework.
So the solution is overriding the read_only field in the serializer as follow:
class ProductlistSerializer(serializers.ModelSerializer):
productlist_id = serializers.IntegerField(read_only=False)
class Meta:
model = Productlist
fields = ('productlist_id', 'productlist_name',)
Currently i'm using a custom marshaller to render json:
JSON.registerObjectMarshaller( PlannedEvent) { PlannedEvent pe ->
return [
id : pe.id,
event : [
eventId : pe.baseEvent.id,
subject : pe.baseEvent.subject
],
//rooms: pe.rooms
rooms: [
id : pe.rooms
]
]
}
This gives the following output:
{"id":1,"event":{"eventId":626,"subject":"Baggeren hc (Kv2)"},"rooms":{"id":[8]}}
I would like to know how to set up my map so the value for rooms is a list with each index representing a separate room. Instead of keys, like "id", being set up as a list. The solution in comment gives to much information about the room, so I can't use that.
Wanted result :
"rooms":[{"id":8},{"id":9}]}
If i am correct for your domain modelling you have a hasMany relationship between PlannedEvent & Room .So if you want to get only the list of ROOMS as IDs from the PlannedEvent domian you need to register custom marshallar for Rooms also like :
JSON.registerObjectMarshaller( Room) { Room room ->
// Map of IDs is returned from here
def returnMap = [:]
returnMap["id"] = room.id
return returnMap
}
And in PlannedEvent:
JSON.registerObjectMarshaller( PlannedEvent) { PlannedEvent pe ->
// This is also a map but the value of key rooms will contain the list of IDs of each room
def roomList = [:]
roomList["rooms"] = pe.rooms
return roomList
}
And for next time please clarify your domians & also what do you want clearly.