Django: Make a GET Request to a URL that is advanced - django

So I have Chat Rooms and I have Messages. Then I have two urls: /messages and /rooms. And these display all your rooms and messages. Also a message can be assigned to a room. So in the Room API I have the messages assigned to that room.
Let's say that the room is called 'Room1' and the messages are 'hey', 'yo' and 'wassup'. If I make a request to just /messages I will get all of the messages. Let's say that only two of the messages are assigned to 'Room1' and the other message is assigned to another room not named.
I want a way to make a get request and only get those two messages assigned to 'Room1 with id = 3' (localhost:8000/rooms/3/messages) instead of: (localhost:8000/messages).
This is an example of when I make a get request to /rooms/3/
{
"id": 3,
"name": "Room 1",
"members": [
{
"id": 1,
"username": "william"
},
{
"id": 2,
"username": "eric"
},
{
"id": 3,
"username": "ryan"
}
],
"messages": [
{
"id": 7,
"content": "hej",
"date": "2019-07-08",
"sender": {
"id": 1,
"username": "william"
}
},
{
"id": 8,
"content": "yoyo",
"date": "2019-07-08",
"sender": {
"id": 2,
"username": "eric"
}
},
{
"id": 9,
"content": "tjo bror",
"date": "2019-07-08",
"sender": {
"id": 3,
"username": "ryan"
}
},
{
"id": 10,
"content": "hej jag heter Eric och jag gar pa polhemskolan i lund och jag ar 17 ar gammal",
"date": "2019-07-08",
"sender": {
"id": 2,
"username": "eric"
}
},
{
"id": 11,
"content": "vi vet hahah",
"date": "2019-07-09",
"sender": {
"id": 1,
"username": "william"
}
},
{
"id": 12,
"content": "amen sluta",
"date": "2019-07-09",
"sender": {
"id": 2,
"username": "eric"
}
}
]
}
This is what I want to get in response if I do rooms/3/messages:
"messages": [
{
"id": 7,
"content": "hej",
"date": "2019-07-08",
"sender": {
"id": 1,
"username": "william"
}
},
{
"id": 8,
"content": "yoyo",
"date": "2019-07-08",
"sender": {
"id": 2,
"username": "eric"
}
},
{
"id": 9,
"content": "tjo bror",
"date": "2019-07-08",
"sender": {
"id": 3,
"username": "ryan"
}
},
{
"id": 10,
"content": "hej jag heter Eric och jag gar pa polhemskolan i lund och jag ar 17 ar gammal",
"date": "2019-07-08",
"sender": {
"id": 2,
"username": "eric"
}
},
{
"id": 11,
"content": "vi vet hahah",
"date": "2019-07-09",
"sender": {
"id": 1,
"username": "william"
}
},
{
"id": 12,
"content": "amen sluta",
"date": "2019-07-09",
"sender": {
"id": 2,
"username": "eric"
}
}
]
}
Django Models:
class UserProfile(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
verbose_name_plural = 'All Users'
def __str__(self):
return self.user.username
#receiver(post_save, sender=User)
def create_user_data(sender, update_fields, created, instance, **kwargs):
if created:
user = instance
profile = UserProfile.objects.create(user=user)
class Message(models.Model):
sender = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name="sendermessage")
content = models.CharField(max_length=500)
date = models.DateField(default=date.today)
canview = models.ManyToManyField(UserProfile, blank=True, related_name="messagecanview")
class Meta:
verbose_name_plural = 'Messages'
def __str__(self):
return "{sender}".format(sender=self.sender)
class Room(models.Model):
name = models.CharField(max_length=50)
members = models.ManyToManyField(UserProfile, blank=True)
messages = models.ManyToManyField(Message, blank=True)
class Meta:
verbose_name_plural = 'Rooms'
def __str__(self):
return "{name}".format(name=self.name)enter code here
Django Serializers:
class UserProfileSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
class Meta:
model = UserProfile
fields = ('id', 'username')
class MessageSerializer(serializers.ModelSerializer):
sender = UserProfileSerializer()
class Meta:
model = Message
fields = ('id', 'content', 'date', 'sender')
class RoomSerializer(serializers.ModelSerializer):
messages = MessageSerializer(many=True)
members = UserProfileSerializer(many=True)
class Meta:
model = Room
fields = ('id', 'name', 'members', 'messages')
Django Views:
class UserProfileView(viewsets.ModelViewSet):
http_method_names = ['get', 'post', 'put', 'delete', 'patch']
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
class MessageView(viewsets.ModelViewSet):
http_method_names = ['get', 'post', 'put', 'delete', 'patch']
queryset = Message.objects.all()
serializer_class = MessageSerializer
class UserMessageView(MessageView):
def get_queryset(self):
return Message.objects.filter(canview__user=self.request.user)
class RoomView(viewsets.ModelViewSet):
http_method_names = ['get', 'post', 'put', 'delete', 'patch']
queryset = Room.objects.all()
serializer_class = RoomSerializer
class UserRoomView(RoomView):
def get_queryset(self):
return Room.objects.filter(members__user=self.request.user)
Django Urls:
router = routers.DefaultRouter()
router.register('users', views.UserProfileView),
router.register('rooms', views.UserRoomView),
router.register('messages', views.UserMessageView),
urlpatterns = [
path('', include(router.urls)),
]

To get all Messages assigned to a room, let's:
Install django-filter:
pip install django-filter
Modify the Room model to specify a related_name:
class Room(models.Model):
name = models.CharField(max_length=50)
members = models.ManyToManyField(UserProfile, blank=True)
messages = models.ManyToManyField(Message, blank=True, related_name='rooms')
# ^^^^^^^^^^^^^^^^^^^^^^
Enable filtering for the rooms related field:
import django_filters
import rest_framework.filters
[...]
class MessageView(viewsets.ModelViewSet):
# vvvvvvvvvvv I don't think this line is needed vvvvvvvvvvvvvv
# http_method_names = ['get', 'post', 'put', 'delete', 'patch']
queryset = Message.objects.all()
serializer_class = MessageSerialize
filter_backends = (
django_filters.rest_framework.DjangoFilterBackend,
rest_framework.filters.OrderingFilter,
)
filter_fields = ['rooms']
Then, you can request all messages for that room with a GET to:
localhost:8000/messages/?rooms=3
Comment question:
You also need to expose the Message object's sender field. Currently it is aliased:
class MessageSerializer(serializers.ModelSerializer):
# vvvv vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
sender_obj = UserProfileSerializer(source='sender', read_only=True)
class Meta:
model = Message
fields = ('id', 'content', 'date', 'sender', 'sender_obj')
# ^^^^^^^^^^^^^^
Then you can POST to /message with the data {"content": "blah", "date": "2019-07-09","sender": 1}

Related

Serialize ManyToManyField with foreign key of nested

I need to serialize attributes of an item related from many to many, but grouped by each type of attribute.
Models
from django.db import models
class AtributoTipo(models.Model):
nombre = models.CharField(max_length=40)
slug = models.SlugField(max_length=40, unique=True)
class Atributo(models.Model):
tipo = models.ForeignKey(AtributoTipo, on_delete=models.CASCADE)
nombre = models.CharField(max_length=40)
slug = models.SlugField(max_length=40)
class Articulo(models.Model):
codigo = models.CharField(max_length=18, unique=True, db_index=True)
nombre = models.CharField(max_length=255)
atributos = models.ManyToManyField(Atributo, related_name="articulos")
Normal DRF serializers:
from rest_framework import serializers
class AtributoTipoSerializer(serializers.ModelSerializer):
class Meta:
model = AtributoTipo
fields = ["__all__"]
class AtributoSerializer(serializers.ModelSerializer):
tipo_slug = serializers.ReadOnlyField(source="tipo.slug")
tipo_nombre = serializers.ReadOnlyField(source="tipo.nombre")
class Meta:
model = Atributo
fields = ["__all__"]
class ArticuloSerializer(serializers.ModelSerializer):
atributos = AtributoSerializer(many=True, read_only=True) # the dude
class Meta:
model = Articulo
fields = ["__all__"]
This is the result with serializers without any weird method:
{
"id": 44906,
"codigo": "DE0058751",
"atributos": [
{
"id": 15107,
"tipo": 76,
"tipo_slug": "talla",
"tipo_nombre": "Talla",
"nombre": "39",
"slug": "39"
},
{
"id": 43454,
"tipo": 76,
"tipo_slug": "talla",
"tipo_nombre": "Talla",
"nombre": "40",
"slug": "40"
},
{
"id": 23234,
"tipo": 15,
"tipo_slug": "color",
"tipo_nombre": "Color",
"nombre": "Rojo",
"slug": "rojo"
},
{
"id": 12408,
"tipo": 15,
"tipo_slug": "color",
"tipo_nombre": "Color",
"nombre": "Verde",
"slug": "verde"
}
]
}
Desired result:
{
"id": 44906,
"codigo": "DE0058751",
"atributos": [
{
"id": 76,
"slug": "talla",
"nombre": "Talla",
"atributos": [
{
"id": 15107,
"tipo": 76,
"nombre": "39",
"slug": "39"
},
{
"id": 12408,
"tipo": 76,
"nombre": "40",
"slug": "40"
}
]
},
{
"id": 15,
"slug": "color",
"nombre": "Color",
"atributos": [
{
"id": 34234,
"tipo": 15,
"nombre": "Rojo",
"slug": "rojo"
},
{
"id": 2323,
"tipo": 15,
"nombre": "Verde",
"slug": "verde"
}
]
}
]
}
The problem is that you lose the context of the article.
I tried itertools groupby, but I think there must be another logic with DRF.
You can redefine to_representation of ListSerializer and use ListSerializer as a field of your ArticuloSerializer:
class GroupedAttributosSerializer(serializers.ListSerializer):
def to_representation(self, data):
data = super().to_representation(data)
tipo_slug_to_objects = defaultdict(list)
for d in data:
tipo_slug_to_objects[d["tipo_slug"]].append(d)
result = []
for tipo_slug, objects in tipo_slug_to_objects.items():
result.append(
{
"id": objects[0]["tipo"],
"slug": tipo_slug,
"nombre": objects[0]["tipo_nombre"],
"attributos": [
{"id": attribute["id"],
"tipo": attribute["tipo"],
"nombre": attribute["nombre"],
"slug": attribute["slug"]}
for attribute in objects
]
}
)
return result
class ArticuloSerializer(serializers.ModelSerializer):
atributos = GroupedAttributosSerializer(child=AtributoSerializer(), read_only=True, default=[])
class Meta:
model = Articulo
fields = ["__all__"]

Django-Rest-Framework POST request to ManyToMany Field

I have a django model that is a message. It has a name which is a CharField, then also an array of users which is a ManyToManyField.
So This is what my API looks like:
[
{
"id": 13,
"content": "hej",
"date": "2019-07-09",
"sender": {
"id": 1,
"username": "william"
}
},
{
"id": 14,
"content": "hej william",
"date": "2019-07-09",
"sender": {
"id": 3,
"username": "ryan"
}
}
]
What I've tried to send via postman POST:
{
"content": "Hello",
"sender": {"username": "william"},
"date": "2019-09-02"
}
The Error I get:
sqlite3.IntegrityError: NOT NULL constraint failed: chat_message.sender_id
ManyToManyField(Userprofile=User):
class Message(models.Model):
sender = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name="sendermessage")
content = models.CharField(max_length=500)
date = models.DateField(default=date.today)
canview = models.ManyToManyField(UserProfile, blank=True, related_name="messagecanview")
class Meta:
verbose_name_plural = 'Messages'
def __str__(self):
return "{sender}".format(sender=self.sender)
Assuming that you have a MessageSerializer class implemented, you could override its create() method in order to support writable nested representations:
class MessageSerializer(serializers.ModelSerializer):
...
def create(self, validated_data):
sender_data = validated_data.pop('sender')
sender = UserProfile.objects.create(**sender_data)
return Message.objects.create(sender=sender, **validated_data)
You pop the sender data from the dictionary, create a UserProfile instance with the attributes in there and then attach it to your Message creation.
This will resolve your error since now there is a real sender created before the actual message has been saved.

Post Request to Django Rest Api that has a filter. So messages/?room=4

My Backend is built like this. Every 'Room' has 'Messages' And Every Message has a Sender (person who sends it).
I want to make a post request to messages/?room=4. So I want to basically add a message directly to the room with id=4.
//models
class UserProfile(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
verbose_name_plural = 'All Users'
def __str__(self):
return self.user.username
#receiver(post_save, sender=User)
def create_user_data(sender, update_fields, created, instance, **kwargs):
if created:
user = instance
profile = UserProfile.objects.create(user=user)
class Message(models.Model):
sender = models.ForeignKey(UserProfile, on_delete=models.CASCADE, related_name="sendermessage")
content = models.CharField(max_length=500)
date = models.DateField(default=date.today)
canview = models.ManyToManyField(UserProfile, blank=True, related_name="messagecanview")
class Meta:
verbose_name_plural = 'Messages'
def __str__(self):
return "{sender}".format(sender=self.sender)
class Room(models.Model):
name = models.CharField(max_length=50)
members = models.ManyToManyField(UserProfile, blank=True, related_name='room')
messages = models.ManyToManyField(Message, blank=True, related_name='room')
class Meta:
verbose_name_plural = 'Rooms'
def __str__(self):
return "{name}".format(name=self.name)
//serializers, views
class UserProfileSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
class Meta:
model = UserProfile
fields = ('id', 'username')
class MessageSerializer(serializers.ModelSerializer):
sender_obj = UserProfileSerializer(source='sender', read_only=True)
class Meta:
model = Message
fields = ('id', 'content', 'date', 'sender', 'sender_obj')
class RoomSerializer(serializers.ModelSerializer):
messages = MessageSerializer(many=True, read_only=True)
members = UserProfileSerializer(many=True, read_only=True)
class Meta:
model = Room
fields = ('id', 'name', 'members', 'messages')
class UserProfileView(viewsets.ModelViewSet):
http_method_names = ['get', 'post', 'put', 'delete', 'patch']
queryset = UserProfile.objects.all()
serializer_class = UserProfileSerializer
filter_backends = (
django_filters.rest_framework.DjangoFilterBackend,
rest_framework.filters.OrderingFilter,
)
filter_fields = ['room']
class MessageView(viewsets.ModelViewSet):
http_method_names = ['get', 'post', 'put', 'delete', 'patch']
queryset = Message.objects.all()
serializer_class = MessageSerializer
filter_backends = (
django_filters.rest_framework.DjangoFilterBackend,
rest_framework.filters.OrderingFilter,
)
filter_fields = ['room']
class UserMessageView(MessageView):
def get_queryset(self):
return Message.objects.filter(canview__user=self.request.user)
class RoomView(viewsets.ModelViewSet):
http_method_names = ['get', 'post', 'put', 'delete', 'patch']
queryset = Room.objects.all()
serializer_class = RoomSerializer
class UserRoomView(RoomView):
def get_queryset(self):
return Room.objects.filter(members__user=self.request.user)
API all messages:
[
{
"id": 7,
"content": "hej",
"date": "2019-07-08",
"sender": 1,
"sender_obj": {
"id": 1,
"username": "william"
}
},
{
"id": 8,
"content": "yoyo",
"date": "2019-07-08",
"sender": 2,
"sender_obj": {
"id": 2,
"username": "eric"
}
},
{
"id": 9,
"content": "tjo bror",
"date": "2019-07-08",
"sender": 3,
"sender_obj": {
"id": 3,
"username": "ryan"
}
},
{
"id": 10,
"content": "hej jag heter Eric och jag gar pa polhemskolan i lund och jag ar 17 ar gammal",
"date": "2019-07-08",
"sender": 2,
"sender_obj": {
"id": 2,
"username": "eric"
}
},
{
"id": 11,
"content": "vi vet hahah",
"date": "2019-07-09",
"sender": 1,
"sender_obj": {
"id": 1,
"username": "william"
}
},
{
"id": 12,
"content": "amen sluta",
"date": "2019-07-09",
"sender": 2,
"sender_obj": {
"id": 2,
"username": "eric"
}
},
{
"id": 13,
"content": "hej",
"date": "2019-07-09",
"sender": 1,
"sender_obj": {
"id": 1,
"username": "william"
}
},
{
"id": 14,
"content": "hej william",
"date": "2019-07-09",
"sender": 3,
"sender_obj": {
"id": 3,
"username": "ryan"
}
}
]
API messages/?room=4
[
{
"id": 7,
"content": "hej",
"date": "2019-07-08",
"sender": 1,
"sender_obj": {
"id": 1,
"username": "william"
}
},
{
"id": 13,
"content": "hej",
"date": "2019-07-09",
"sender": 1,
"sender_obj": {
"id": 1,
"username": "william"
}
},
{
"id": 14,
"content": "hej william",
"date": "2019-07-09",
"sender": 3,
"sender_obj": {
"id": 3,
"username": "ryan"
}
}
]
If someone has a solution it would help loads. I need to get this to work because its a chat application I am developing on flutter- mobile.
Change your MessageSerializer to expose the room related_field ( which is a ManyToManyField and thus each message can be associated to multiple rooms...):
class MessageSerializer(serializers.ModelSerializer):
[...]
fields = ('id', 'content', 'date', 'sender', 'sender_obj', 'room')
# ^^^^^^^^
POST to your /message interface like:
{
"content": "foodefafa",
"sender": 17,
"rooms":[42]
}

Django-Tastypie self children

Trying to make api for multiple subtasks.
I have the task model, that can have another task as a parent:
class Task(models.Model):
parent_task = models.ForeignKey("Task", null=True, blank=True)
name = models.CharField(max_length=64)
def __unicode__ (self):
return self.name
Now I'm trying to make tastypie resource:
class TaskResource(ModelResource):
parent_task = fields.ForeignKey(TaskResource, 'parent_task', full=False) <-- ERROR HERE
class Meta:
queryset = Task.objects.all()
resource_name = 'task'
list_allowed_methods = ['get', 'put', 'post', 'delete']
include_resource_uri = False
def dehydrate(self, bundle, for_list=False):
bundle.data["subtasks"] = "how?" <-- HOW??
return bundle
Thanks for your time.
P.S. I need something like this:
[
{
"id": 1,
"name": "Task 1",
"subtasks": [
{
"id": 1,
"name": "Task 1",
"subtasks": [...]
}
]
},
{
"id": 2,
"name": "Task 2",
"subtasks": "how?"
}
]
Almost a copy of Including child resources in a Django Tastypie API but not exactly.
So your first problem is that you specify relation to self wrong. It should be just self:
parent_task = fields.ForeignKey('self', 'parent_task', null=True, full=False)
Secondly, notice null=True - parent could be null.
Lastly, you just need to add another relation field and ask for the full details
subtasks = fields.ToManyField('self', 'task_set', full=True)
task_set is a related_name for the Task.parent_task field.
The resulting code is:
class TaskResource(ModelResource):
parent_task = fields.ForeignKey('self', 'parent_task', null=True, full=False)
subtasks = fields.ToManyField('self', 'subtasks', full=True)
class Meta:
queryset = Task.objects.all()
resource_name = 'task'
list_allowed_methods = ['get', 'put', 'post', 'delete']
include_resource_uri = False
And the result:
{
"meta": {
"previous": null,
"total_count": 3,
"offset": 0,
"limit": 20,
"next": null
},
"objects": [
{
"parent_task": null,
"subtasks": [
{
"parent_task": "/api/v1/task/1/",
"subtasks": [],
"id": 2,
"name": "Root's Child 1"
},
{
"parent_task": "/api/v1/task/1/",
"subtasks": [],
"id": 3,
"name": "Root's Child 2"
}
],
"id": 1,
"name": "Root Task"
},
{
"parent_task": "/api/v1/task/1/",
"subtasks": [],
"id": 2,
"name": "Root's Child 1"
},
{
"parent_task": "/api/v1/task/1/",
"subtasks": [],
"id": 3,
"name": "Root's Child 2"
}
]
}

Distinct field Rest Framework Django

I need to make a distinct with a field of my model and not how to make
My model is:
class CheckList(CoreModel):
date = models.DateTimeField(default=datetime.now, blank=True, null=True, verbose_name=_('Date'))
establishment = models.ForeignKey(Establishment, related_name="checklists", on_delete=models.CASCADE, null=True, verbose_name=_('Establishment'))
user = models.ForeignKey(ITManager, related_name="checklists", on_delete=models.CASCADE, null=True, verbose_name=_('User'))
class Meta:
verbose_name_plural = _("Checklist")
verbose_name = _("Checklists")
def __str__(self):
return str(self.date)
My serializer and view:
class CheckListSerializer(BulkSerializerMixin, serializers.ModelSerializer):
user = ITManagerSerializer()
class Meta:
model = CheckList
list_serializer_class = BulkListSerializer
fields = ['id', 'user', 'establishment', 'date']
class ChecklistBulkViewSet(BulkModelViewSet):
queryset = CheckList.objects.values('establishment', 'user', 'date').distinct()
model = CheckList
serializer_class = CheckListSerializer
filter_class = ChecklistFilter
The api return me:
"results": [
{
"id": 1,
"user": {
"id": 3,
"first_name": "Andres",
"last_name": "Gallardo",
"rut": "21312",
"email": null,
"user_name": "andres",
"password": null,
"user": 4,
"country": [],
"active": true
},
"establishment": 3,
"date": "2016-06-14T15:15:00Z"
},
{
"id": 2,
"user": {
"id": 2,
"first_name": "Ramiro",
"last_name": "Gutierrez",
"rut": "15616+",
"email": null,
"user_name": null,
"password": null,
"user": 2,
"country": [
{
"id": 1,
"name": "Argentina",
"code_area": null
}
],
"active": false
},
"establishment": 3,
"date": "2016-06-09T15:40:04Z"
}]
I need you just leave me an establishment with the same id
any suggestions??
Thanks !