How to delete any filed in ViewSet - django-rest-framework

class DepartSerializer(serializers.HyperlinkedModelSerializer):
attrs = AttrSerializer(source="depattr", many=True)
people = PeopleSerializer(source='perdepart', many=True)
class Meta:
model = Departs
fields = ('url', 'name', 'describe', 'pinyin', 'attrs', 'people')
class DepartsViewSet(viewsets.ReadOnlyModelViewSet):
"""
i want delete people field in List , and Retain people fieled in retrieve.
"""
queryset = Departs.objects.filter(disabled=False).order_by('-uptime')
serializer_class = DepartSerializer
1.I want the result like this:
2.get /depart
[
{"name":"depart1","id":1},
{"name":"depart2","id":2},
]
3.get /depart/1
{
"name": "depart1",
"id": 1,
"people": [{
"id": 1,
"name": "per1"
},
{
"id": 2,
"name": "per2"
}
]
}

You can use different serializers in your viewset depending on the action by overriding the viewset's get_serializer_class:
def get_serializer_class(self):
if self.action == 'retrieve':
return DepartSerializerWithoutPeople
return DepartSerializer
retrieve is the action called by get /depart/1/. And then you can define your DepartSerializerWithoutPeople like this:
class DepartSerializerWithoutPeople(serializers.HyperlinkedModelSerializer):
attrs = AttrSerializer(source="depattr", many=True)
class Meta:
model = Departs
fields = ('url', 'name', 'describe', 'pinyin', 'attrs',)

Related

Alter Queryset before returning in DRF

I have a model structure similar to the one below:
Store -> some store fields
Books -> some book fields, FK to Store
BookProperty -> name, value, FK to Books (a one to many relationship), FK to store
The book property can store any info for the book eg. no_of_pages, publisher etc added by the store.
I need to make an API endpoint where I can get all BookProperty for a store.
I used the url:
/stores/:store_id/bookproperty
Used a ModelSerializer for BookProperty with fields = [publisher, no_of_pages]
Used a genericViewSet with a ListModelMixin.
The endpoint turned out like this below:
{
"count": 4,
"next": null,
"previous": null,
"results": [
{
"name": "publisher",
"value": "somePublisher"
},
{
"name": "pages",
"value": "1000"
},
{
"name": "publisher",
"value": "someOtherPublisher"
},
{
"name": "publisher",
"value": "somePublisher"
}
]
}
The problem with this is that multiple objects can have the same name, value pairs. I need this information in a way where all the objects are unique and grouped kind of like this:
{
{"name":"publisher", "value":["somePublisher", "someOtherPublisher"]},
{"name":"pages", "value":["1000"]},
}
I'm trying to override the get_queryset(self) but it's not working.
Any help would be appreciated. Thanks in advance!
EDIT:
models.py
class BookProperty(models.Model):
books = models.ForeignKey(
Books,
on_delete=models.CASCADE,
)
name = models.CharField(max_length=100)
value = models.CharField(max_length=100)
store = models.ForeignKey(
"Store",
on_delete=models.CASCADE,
)
serializers.py
class BookPropertySerializer(serializers.ModelSerializer):
class Meta:
model = models.BookProperty
fields = ["name", "value"]
views.py
class BookPropertyViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
serializer_class = serializers.BookPropertySerializer
I think that instead of overriding the get_queryset(self) I should try changing the def list(self, request, *args, **kwargs) :
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
tag_dictionary = defaultdict()
things = list(queryset)
for key, group in itertools.groupby(things, lambda x: x.name):
for thing in group:
if key not in tag_dictionary.keys():
tag_dictionary[key] = [thing.value]
else:
tag_dictionary[key].append(thing.value)
for key in tag_dictionary.keys():
tag_dictionary[key] = list(set(tag_dictionary[key]))
return Response(json.dumps(tag_dictionary))
The above solution is working but might not be the best one.

Nested serializer doesn't pick up correct ID

There are two models, they are defined this way:
class ShoppingList(models.Model):
id = models.CharField(max_length=40, primary_key=True)
name = models.CharField(max_length=40)
session_id = models.CharField(max_length=40)
config_file = models.FileField(upload_to=upload_config_file)
def __str__(self):
return self.id
class FetchedData(models.Model):
model_id = models.CharField(max_length=40)
config_id = models.ForeignKey(BillOfMaterial, on_delete=models.CASCADE, default=0)
config_name = models.CharField(max_length=40)
def __str__(self):
return self.model_id
And serialized like this:
class FetchedDataSerializer(serializers.ModelSerializer):
file_fields = serializers.SerializerMethodField()
class Meta:
model = FetchedData
fields = ('model_id', 'config_id', 'config_name', 'file_fields')
def get_file_fields(self, obj):
print(obj)
# queryset = ShoppingList.objects.filter(config_file = obj) ## (1)
queryset = BillOfMaterial.objects.all() ## (2)
return [ShoppingListSerializer(cf).data for cf in queryset]
I was advised* to implement the solution marked as (1) in the serializer above, but when it's on, I get responses with an empty array, for example:
[
{
"model_id": "6553",
"config_id": "2322",
"config_name": "Config No. 1",
"file_fields": []
}
]
Meanwhile, with option (2) turned on and option (1) commented out, I get all the instances displayed:
[
{
"model_id": "6553",
"config_id": "2322",
"config_name": "Config No. 1",
"file_fields": [
{
"id": "2322",
"name": "First Example",
"session_id": "9883",
"config_file": "/uploads/2322/eq-example_7DQDsJ4.json"
},
{
"id": "4544",
"name": "Another Example",
"session_id": "4376",
"config_file": "/uploads/4544/d-jay12.json"
}
]
}
]
The print(obj) method always gives a model_id value. And it should output file_fields.id, I guess.
How should I re-build this piece of code to be able to display only the file_field with id matching config_id of the parent?
*This is a follow-up of an issue described here: TypeError: 'FieldFile' object is not callable
In FetchedData model I added this method:
def config_link(self):
return self.config_id.config_file
(it binds config_file from ShoppingList model).
FetchedDataSerializer should then look like this:
class FetchedDataSerializer(serializers.ModelSerializer):
file_link = serializers.SerializerMethodField()
class Meta:
model = FetchedData
fields = ('model_id', 'config_id', 'config_name', 'file_link')
def get_file_link(self, obj):
return obj.config_link()

In the Django Rest Framework, how do you add ManyToMany related objects?

Here's my code:
Models
class Recipe(models.Model):
name = models.CharField(max_length=50, unique=True)
ingredient = models.ManyToManyField(Ingredient)
class Ingredient(models.Model):
name = models.CharField(max_length=50, unique=True)
View
class RecipeDetailAPIView(RetrieveUpdateDestroyAPIView):
permission_classes = (IsAdminOrReadOnly,)
serializer_class = RecipeSerializer
queryset = Recipe.objects.all()
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def perform_update(self, serializer):
serializer.save(updated_by_user=self.request.user)
Serializers
class IngredientSerializer(serializers.ModelSerializer):
class Meta:
model = Ingredient
fields = [
'id',
'name',
]
class RecipeSerializer(serializers.ModelSerializer):
ingredient = IngredientSerializer(many=True, read_only=False)
class Meta:
model = Recipe
fields = [
'id',
'name',
'ingredient',
]
I'm starting with the following Recipe object:
{
"id": 91
"name": "Potato Salad"
"ingredient": [
{
"id": 5,
"name": "Potato"
}
]
}
Now, I am attempting to update that object by putting the following JSON object to the IngredientSerializer:
{
"id": 91
"name": "Potato Salad"
"ingredient": [
{
"id": 5,
"name": "Potato"
},
{
"id": 6,
"name": "Mayo"
}
]
}
What I want is it to recognize that the relationship to Potato already exists and skip over that, but add a relationship to the Mayo object. Note that the Mayo object already exists in Ingredients, but is not yet tied to the Potato Salad object.
What actually happens is the Serializer tries to create a new Ingredient object and fails because "ingredient with this name already exists."
How do I accomplish this?
DRF does not have any automatic "write" behavior for nested serializers, precisely because it does not know things like how to go about updates in the scenario you mentioned. Therefore, you need to write your own update method in your RecipeSerializer.
class IngredientSerializer(serializers.ModelSerializer):
def validate_name(self, value):
# manually validate
pass
class Meta:
model = Ingredient
fields = ['id', 'name']
extra_kwargs = {
'name': {'validators': []}, # remove uniqueness validation
}
class RecipeSerializer(serializers.ModelSerializer):
ingredient = IngredientSerializer(many=True, read_only=False)
def update(self, instance, validated_data):
ingredients = validated_data.pop('ingredient')
# ... logic to save ingredients for this recipe instance
return instance
class Meta:
model = Recipe
fields = ['id', 'name', 'ingredient']
Relevant DRF documentation:
Saving Instances
Writable Nested Serializer
Updating nested serializers
Update:
If DRF validation fails for the uniqueness constraint in the name field, you should try removing validators for that field.
Alternative solution: Only use full serializer as read only field
You can change you RecipeSerializer to the following:
class RecipeSerializer(serializers.ModelSerializer):
ingredient_details = IngredientSerializer(many=True, read_only=True, source='ingredient')
class Meta:
model = Recipe
fields = ['id', 'name', 'ingredient', 'ingredient_details']
And that's it. No need to override update or anything. You'll get the detailed representation when you get a recipe, and you can just PUT with the ingredient ids when updating. So your json when updating will look something like this:
{
"id": 91
"name": "Potato Salad"
"ingredient": [5, 6]
}

Getting rest history from Django simple History

I am using django-simple-history (1.8.1) and DRF (3.5.3). I want to get a rest service containing the history of each element. Let's take an example !
models.py
class Product(models.Model):
name = models.CharField(max_length=50)
price = models.IntegerField()
history = HistoricalRecords()
def __str__(self):
return self.name
So, what must be serializers.py ? I'd like to GET something like :
[
{
"id": 1,
"name": "Apple",
"price": 8,
"history": [
{
"history_id": 1,
"id": 1,
"name": "Apple",
"price": 0,
"history_date": "2016-11-22T08:02:08.739134Z",
"history_type": "+",
"history_user": 1
},
{
"history_id": 2,
"id": 1,
"name": "Apple",
"price": 10,
"history_date": "2016-11-22T08:03:50.845634Z",
"history_type": "~",
"history_user": 1
},
{
"history_id": 3,
"id": 1,
"name": "Apple",
"price": 8,
"history_date": "2016-11-22T08:03:58.243843Z",
"history_type": "~",
"history_user": 1
}
]
}
]
After searching whitout finding the solution, I finally found it by myself. But if someone have a better solution...
I know it's been a year, but anyway, maybe someone finds it useful. Here is my solution (it seems far easier to me):
A new serializer field:
class HistoricalRecordField(serializers.ListField):
child = serializers.DictField()
def to_representation(self, data):
return super().to_representation(data.values())
Now simply use it as a a field in your serializer:
history = HistoricalRecordField(read_only=True)
This makes use of DRF's built in list and dict serializers, only trick is to pass it the correct iterable, which is being done by calling .values() on the simple-history model manager class.
Here's my solution.
In serializers.py :
from rest_framework import serializers
from .models import Product
class sHistory(serializers.ModelSerializer):
def __init__(self, model, *args, fields='__all__', **kwargs):
self.Meta.model = model
self.Meta.fields = fields
super().__init__()
class Meta:
pass
class sProduct(serializers.ModelSerializer):
class Meta:
model = Product
fields = '__all__'
history = serializers.SerializerMethodField()
def get_history(self, obj):
model = obj.history.__dict__['model']
fields = ['history_id', ]
serializer = sHistory(model, obj.history.all().order_by('history_date'), fields=fields, many=True)
serializer.is_valid()
return serializer.data
It works ! I'm quite proud about it ! any suggestions ?
There seems to be an even clearer and simpler way
class AnySerializer(serializers.ModelSerializer):
history = serializers.SerializerMethodField()
class Meta:
model = MyModel
fields = (....
....
'history',
)
read_only_fields = ('history',)
def get_history(self, obj):
# using slicing to exclude current field values
h = obj.history.all().values('field_name')[1:]
return h
You can create a serializer like this:
class ProductHistorySerializer(serializers.ModelSerializer):
class Meta:
model = Product.history.model
fields = '__all__'
Then in view, You can have the code below:
#...
logs = ProductHistorySerializer(Product.history.filter(price__gt=100), many=True)
return Response({'isSuccess': True, 'data': logs.data})

Django Rest Framework: How to Append to array

I have a user object where I want to add a new friend onto their existing friends list. If below is the existing friends list for user1 , how would I POST/PUT a new friend onto that existing list without reposting the whole friends list.
{
"friends": [
{
"first_name": "Bob"
},
{
"first_name": "Dave"
},
{
"first_name": "Jon"
}
],
"first_name": "User1",
}
What would my JSON look like in the POST and what do I have to do on the DRF end to allow me to update this array without reposting the existing array.
I tried to POST the following but it just overwrote the entire friend list
PATCH /api/profile/55522221111/ HTTP/1.1
{"friends":[{"first_name":"new friend"}]}
An alternative to your strategy could be redefining your models so as to create a one-to-many reference of the inner objects in the array, which in your example has {"first_name": "Bob"} in it to the main object.
This would allow you to post into the friends array just by mentioning a foreign key in model.
Something like :
models.py
class User(models.Model):
first_name = models.CharField(max_length=64)
class Friend(models.Model):
first_name = models.CharField(max_length=64)
user = models.ForeignKey(User, related_name='friends')
serializers.py
class FriendSerializer(serializers.ModelSerializer):
class Meta:
model = Friend
fields = ('id', 'first_name', )
class UserSerializer(serializers.ModelSerializer):
friends = FriendSerializer(many=True)
class Meta:
model = User
fields = ('id', 'first_name', 'friends', )
views.py
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
class FriendViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = FriendSerializer
urls.py
router = routers.DefaultRouter()
router.register(r'users', UserViewSet)
router.register(r'friends', FriendViewSet)
urlpatterns = [
url(r'^api/', include(router.urls)),
]
This should result in 2 urls like (1) /api/users/ and (2) /api/friends/ and you can post into (2) and if references are correct it will show as you wish in (1).
{
"friends": [
{
"first_name": "Bob"
},
{
"first_name": "Dave"
},
{
"first_name": "Jon"
}
],
"first_name": "User1",
}
You'd be better off creating a new endpoint, something like:
POST /api/profile/555222211111/friends/add/
{
"first_name": "new_friend"
}

Resources