Django-notifications serialize target rest framework - django-rest-framework

I'm trying to add Django-notifications to my drf project. I get response when hitting the endpoint:
[
{
"recipient": {
"id": 274,
"username": "harry",
"first_name": "Harry",
"last_name": "Moreno"
},
"unread": true,
"target": null,
"verb": "invite approved"
}
]
serializers.py
class GenericNotificationRelatedField(serializers.RelatedField):
User = get_user_model()
def to_representation(self, value):
if isinstance(value, Invite):
serializer = InviteSerializer(value)
if isinstance(value, User):
serializer = UserSerializer(value)
return serializer.data
class NotificationSerializer(serializers.Serializer):
recipient = UserSerializer(read_only=True)
unread = serializers.BooleanField(read_only=True)
target = GenericNotificationRelatedField(read_only=True)
How do I make the target non-null?

Turns out the target is null because that is how I created the notification in the model
notify.send(user, recipient=user, verb='you reached level 10')
if I wanted a non-null target I should specify one like
notify.send(user, recipient=user, target=user, verb='you reached level 10')
Note: there is no django view that generates the json in the question.
In our urls.py we wire up the route to the notification view from the app.
path(
"alerts/",
views.NotificationViewSet.as_view({"get": "list"}),
name="notifications",
),
see the installation instructions https://github.com/django-notifications/django-notifications#installation

Related

DjangoREST: Extracting value from POST request to use in overridden create method

I am trying to accomplish a simple task of extracting the value passed from the POST request and using it as a parameter inside the overridden create method.
This is the example POST request body JSON, and "documented" is the field I wish to extract.
### POST
{
"url": "aaaa",
"title": "bbbb",
"publisher": "bbbb",
"desc": "bbbb",
"summary": "bbbb",
"documentId": "onDKe6K"
}
Using validated_data.pop("documentId") was the most obvious choice. However, I need DocumentListingField set to read_only=False in order to use this method. And that option raised larger issues as my Document model has an Hashfield that is not easily serializable.
Is there any other way to accomplish what I want in this situation? I've tried all of these but they all failed with "KeyError: documented"
validated_data.get("documentId")
validated_data["documentId"]
serializers.py
from django.forms.models import model_to_dict
class DocumentListingField(serializers.RelatedField):
def to_representation(self, instance):
return model_to_dict(instance.document)
class MySourceSerializer(serializers.ModelSerializer):
Document = DocumentListingField(many=False, read_only=True)
class Meta:
model = MySource
fields = (
"id",
"url",
"title",
"publisher",
"desc",
"summary",
"Document",
)
def create(self, validated_data):
documentId = validated_data.get("documentId"). <========= LINE OF INTEREST
print(documentId)
source = MySource.objects.create(
document=Document.objects.get(id=documentId), **validated_data
)
print(validated_data)
source.save()
return source
I think you can set the documentId field in the serializer.
class MySourceSerializer(serializers.ModelSerializer):
Document = DocumentListingField(many=False, read_only=True)
documentId = serializers.CharField(write_only = True)
lass Meta:
model = MySource
fields = (
...
"documentId",
)

Django exclude repetition in another request

There are two requests, the first is reading the task by uuid, the second is outputting 3 random tasks from the same user - "recommendations"
The task that is open
{
"id": 4,
"userInfo": 1,
"title": "Comparing numbers",
"uuid": "5a722487"
}
Recommendations for it
Tell me, how to exclude the current task from the second query
[
{
"id": 16,
"userInfo": 1,
"title": "The opposite number",
"uuid": "1e6a7182"
},
{
"id": 19,
"userInfo": 1,
"title": "Number of vowels",
"uuid": "be1320cc"
},
{
**"id": 4, <- exclude this post
"userInfo": 1,
"title": "Comparing numbers",
"uuid": "5a722487"**
}
]
views.py
class PostUuid(generics.ListAPIView):
"""Reading a record by uuid"""
queryset = Task.objects.all()
serializer_class = TaskCreateSerializer
lookup_field = 'uuid'
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
Task.objects.filter(pk=instance.id)
serializer = self.get_serializer(instance)
return Response(serializer.data)
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
class RecommendationTaskView(generics.ListAPIView):
"""Getting a recommendation"""
serializer_class = TaskCreateSerializer
def get_queryset(self):
items = list(Task.objects.filter(
userInfo_id=self.kwargs.get('pk')).select_related('userInfo'))
random_items = random.sample(items, 3)
return random_items
Restful APIs should be stateless. Statelessness means that every HTTP request happens in complete isolation. When the client makes an HTTP request, it includes all information necessary for the server to fulfill the request.
The server never relies on information from previous requests from the client. If any such information is important then the client will send that as part of the current request.
You should send the task id which you want to exclude on the other apis. In this way, you have that id and you can exclude that on the query set.

Design pattern for DRY approach to dynamic serializer fields

Basically, what I want to achieve is to make the list of fields in a serializer be optionally dynamic depending on whether the user has provided the list of fields they are interested in.
Here's my serializer for DRF serializer:
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
fields = self.context['request'].query_params.get('fields')
if fields:
fields = fields.split(',')
allowed = set(fields)
existing = set(self.fields.keys())
for field_name in existing - allowed:
self.fields.pop(field_name)
And my serializer:
class MySerializer(serializer_mixins.DynamicFieldsModelSerializer, serializers.ModelSerializer):
# fields...
This achieves the goal of not including the fields that the user has not mentioned in fields param of the queryset. But! We end up with actual query to the database that fetches the entire set of fields. This issue, in turn, could be solved by just adding the following code to the view:
class Rfs(ListAPIView):
serializer_class = MySerializer
def get_queryset(self):
qs = ...
fields = request.query_params.get('fields')
if fields:
qs = qs.only(*fields.split(','))
return qs
However, fills like two issues issues here:
non-DRY pattern since we have to repeat ourselves both in the view and the serializer
Sometimes it might be the case that the field name inside the queryset does not correspond exactly to the field name of the model.
So maybe there's some more elegant and Django-native solution for this usecase ?
I am using drf_queryfields
In dependence of your query_params your view will be modified
GET http://127.0.0.1:8000/snippets/
[
{
"id": 1,
"title": "",
"code": "foo = \"bar\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
},
{
"id": 2,
"title": "",
"code": "print \"hello, world\"\n",
"linenos": false,
"language": "python",
"style": "friendly"
}
]
GET http://127.0.0.1:8000/snippets/?fields=id,code
[
{
"id": 1,
"code": "foo = \"bar\"\n",
},
{
"id": 2,
"code": "print \"hello, world\"\n",
}
]
I hope that´s it what you would like to achieve.

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.

How do i serialize objects during enrichment with stream-django and django rest framework?

Im using stream-django with django REST framework and the enriched activities are throwing "not JSON serializable" on the objects returned from enrichment, which is as expected as they have not gone through any serializing.
How do i customize the enrichment process so that it returns a serialized object from my drf serializer and not the object itself?
Some example data, not enriched:
"is_seen": false,
"is_read": false,
"group": "19931_2016-04-04",
"created_at": "2016-04-04T08:53:42.601",
"updated_at": "2016-04-04T11:33:26.140",
"id": "0bc8c85a-fa59-11e5-8080-800005683205",
"verb": "message",
"activities": [
{
"origin": null,
"verb": "message",
"time": "2016-04-04T11:33:26.140",
"id": "0bc8c85a-fa59-11e5-8080-800005683205",
"foreign_id": "chat.Message:6",
"target": null,
"to": [
"notification:1"
],
"actor": "auth.User:1",
"object": "chat.Message:6"
}
The view:
def get(self, request, format=None):
user = request.user
enricher = Enrich()
feed = feed_manager.get_notification_feed(user.id)
notifications = feed.get(limit=5)['results']
enriched_activities=enricher.enrich_aggregated_activities(notifications)
return Response(enriched_activities)
I solved it by doing the following:
property tag on the model that returns the serializer class
#property
def activity_object_serializer_class(self):
from .serializers import FooSerializer
return FooSerializer
Then used this to serialize the enriched activities. Supports nesting.
#staticmethod
def get_serialized_object_or_str(obj):
if hasattr(obj, 'activity_object_serializer_class'):
obj = obj.activity_object_serializer_class(obj).data
else:
obj = str(obj) # Could also raise exception here
return obj
def serialize_activities(self, activities):
for activity in activities:
for a in activity['activities']:
a['object'] = self.get_serialized_object_or_str(a['object'])
# The actor is always a auth.User in our case
a['actor'] = UserSerializer(a['actor']).data
return activities
and the view:
def get(self, request, format=None):
user = request.user
enricher = Enrich()
feed = feed_manager.get_notification_feed(user.id)
notifications = feed.get(limit=5)['results']
enriched_activities = enricher.enrich_aggregated_activities(notifications)
serialized_activities = self.serialize_activities(enriched_activities)
return Response(serialized_activities)
The enrich step replaces string references into full Django model instances.
For example: the string "chat.Message:6" is replaced with an instance of chat.models.Message (same as Message.objects.get(pk=6)).
By default DRF does not know how to serialize Django models and fails with a serialization error. Luckily serializing models is a very simple task when using DRF. There is a built-in serializer class that is specific to Django models (serializers.ModelSerializer).
The documentation of DRF explains this process in detail here: http://www.django-rest-framework.org/api-guide/serializers/#modelserializer.
In your case you probably need to use nested serialization and make the serialization of the object field smart (that field can contain references to different kind of objects).
There is an open issue about this on Github: https://github.com/GetStream/stream-django/issues/38. Ideally this is something the library will provide as helper/example, so any code contribution / input will help making that happen.

Resources