How to relate two tables with one of the field equal? - django

I am new with django and django rest framework. Excuse me for my bad english...
I am trying to relate two django models:
First one (class ModulMess) receives messages from remote numbered modules
-moduleA received message "AAA".
-moduleB received message "BBB"
-moduleA received message "CCC"
-moduleC received message "DDD"
Second one (class Owner) is a list of users who own the modules
-UserXX owns moduleA and moduleC
-UserYY owns moduleB
I am tryng to make search filter in order to list this sort of message for actual user:
For example, for UserXX:
UserXX received from moduleA messages "AAA" and "CCC"
and from moduleC message "DDD"
Please could you explain me theorically how to manage it? I am not abble to visualize how to make relations between the two models, as they have one equal field each one...
Thank you very much!
I tried:
class Owner(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE,related_name='usermod')
modulkey = models.CharField(max_length=255)
date_in = models.DateTimeField(default=timezone.now, blank=True)
def __str__(self):
return f'{self.user.first_name} {self.user.last_name} | {self.modulkey}'
class ModulMess(models.Model):
modulkey = models.ForeignKey(Owner, on_delete=models.CASCADE)
date_ini = models.DateTimeField(default=timezone.now, blank=True)
message = models.CharField(max_length=255)
But cannot reach to achieve a good serializer nor view...

I think your modelling is correct. You also need the correct queryset logic and you are set to go. But I'll try to provide the viewset and serializer.
Though the FK modulkey in ModuleMess model can be changed to owner as it makes more sense:
class ModulMess(models.Model):
owner = models.ForeignKey(Owner, on_delete=models.CASCADE)
date_ini = models.DateTimeField(default=timezone.now, blank=True)
message = models.CharField(max_length=255)
The query logic can be defined in a different function like below:
def get_messages_for_user(user):
# Get the owner object for the user
owner = Owner.objects.get(user=user)
# Filter the ModulMess queryset to include only messages for the user's modulkey
messages = ModulMess.objects.filter(owner__modulkey=owner.modulkey)
return messages
Then for serializer we can use model serializer class. Like below:
class ModulMessSerializer(serializers.ModelSerializer):
class Meta:
model = ModulMess
fields = ['id', 'date_ini', 'message', 'owner']
And lastly the viewset will look like below:
class ModulMessViewSet(viewsets.ViewSet):
permission_classes = [IsAuthenticated]
serializer_class = ModulMessSerializer
def list(self, request):
user = request.user
messages = get_messages_for_user(user)
serializer = self.serializer_class(messages, many=True)
return Response(serializer.data)

Related

display only some fields in get api response django serializer

I have an example model which has a fk relation with user model and Blog model. Now I have a get api which only requires certain fields of user to be displayed.
My model:
class Example(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
null=True,
related_name="user_examples",
)
blog = models.ForeignKey(
Blog,
on_delete=models.CASCADE,
null=True,
related_name="blog_examples",
)
/................./
Now my view:
class ExampleView(viewsets.ModelViewSet):
queryset = Example.objects.all()
serializer_class = ExampleSerializer
def list(self, request, *args, **kwargs):
id = self.kwargs.get('pk')
queryset = Example.objects.filter(blog=id)
serializer = self.serializer_class(queryset,many=True)
return Response(serializer.data,status=200)
My serializer:
class ExampleSerializer(serializers.ModelSerializer):
class Meta:
model = Example
fields = ['user','blog','status']
depth = 1
Now when I call with this get api, I get all example objects that is required but all the unnecessary fields of user like password, group etc . What I want is only user's email and full name. Same goes with blog, I only want certain fields not all of them. Now how to achieve this in a best way??
You will have to specify the required fields in nested serializers. e.g.
class BlogSerializer(serializers.ModelSerializer):
class Meta:
model = Blog
fields = ['title', 'author']
class ExampleSerializer(serializers.ModelSerializer):
blog = BlogSerializer()
class Meta:
model = Example
fields = ['user','blog','status']
are you setting depth in serializer's init method or anywhere else? beacause ideally it should only display id's and not anything else. if yes then set depth to zero and use serializer's method field to return data that you need on frontend. I can provide you with example code samples

method to serialize comment(model) data using parent field to get comment's reply in Django

I have made FoodComment model like this in Django using Foreignkey. It uses username to specify user who owns the comment, and food is for menu which to collect comments of users. And parent points other FoodComment object if it is comment's reply.
class FoodComment(models.Model):
username = models.ForeignKey(Account, on_delete=models.CASCADE)
food = models.ForeignKey(Food, on_delete=models.CASCADE)
parent = models.ForeignKey('self', on_delete=models.CASCADE, null=True, blank=True)
body = models.CharField(max_length=30)
star = models.IntegerField(default=5)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.food.menuname + ':' + self.username.username
I am having problem with serializer getting jsoned comments data with hierarchy of comments and that of replies.
class FoodCommentList(APIView):
serializer_class = CommentSerializer
def get(self, request, foodname):
food = Food.objects.get(menuname=foodname)
data = FoodComment.objects.select_related('food').filter(food=food, parent=None)
serialized = CommentSerializer(data, many=True)
return Response(serialized.data, HTTP_200_OK)
If i use views.py's FoodCommentList function as above, REST returns serialized.data as below.
[{"id":1,"body":"delicious!","star":5,"created":"2020-03-26T20:56:49.111307","username":2,"food":19,"parent":null}]
And CommentSerializer used above looks like this.
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = FoodComment
fields = '__all__'
What I am trying to do is not just serialize 'parent=None' data but with parent field, I want to get json data with comment's reply like this using serializer.
{
id:1,
body:"delicious!",
...
childs: {
id:2,
body:"ty!",
...
}
]
I have tried code by other users but couldn't solve it. Is it possible to call CommentSerializer once and get comment's reply in recursive way using serializer?
Thank You!

Polymorphic models serializer

I'm using a Polymorphic model for setting up notifications:
My models:
class Notification(PolymorphicModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
created_by = models.ForeignKey(ElsUser, on_delete=models.CASCADE, default=None, related_name="creatednotifications")
created_on = models.DateTimeField(default=timezone.now)
created_for = models.ForeignKey(ElsUser, on_delete=models.CASCADE, default=None, related_name="receivednotifications")
read = models.DateTimeField(default=None, null=True, blank=True)
message = models.CharField(default=None, blank=True, null=True, max_length=800)
#property
def total(self):
return self.objects.filter(created_for=self.request.user).count()
#property
def unread(self):
return self.objects.filter(created_for=self.request.user,read=None).count()
#property
def read(self):
return self.objects.filter(created_for=self.request.user).exclude(read=None).count()
class WorkflowNotification(Notification):
# permission_transition = models.ForeignKey(WorkflowStatePermissionTransition, on_delete=models.CASCADE)
action = models.ForeignKey(UserAction, on_delete=models.CASCADE)
Currently i have just one model WorkFlowNotification inheriting from the Polymorphic model,but many would be there in the future.
Im trying to get the count(total) of notifications for the logged in user in the API ..total is given as property field to help in the same
my serializer:
class NotificationSerializer(serializers.ModelSerializer):
total = serializers.ReadOnlyField()
read = serializers.ReadOnlyField()
unread = serializers.ReadOnlyField()
class Meta:
model = Notification
fields = ['id', 'total','read', 'unread']
In the view:
class NotificationsMeta(generics.ListAPIView):
serializer_class = NotificationSerializer
queryset = Notification.objects.all()
When i try to run the server it shows:
Got AttributeError when attempting to get a value for field `total` on serializer `NotificationSerializer`.
The serializer field might be named incorrectly and not match any attribute or key on the `WorkflowNotification` instance.
Original exception text was: Manager isn't accessible via WorkflowNotification instances.
Since you need the 'meta data' only, what is the use of making a model serializer? Or any serializer, for that matter? Serializers will give you serialized instances of the objects of your model. So if you have multiple objects, you will get multiple serialized objects in response.
Just make your view a normal APIView. Since there is no necessity of serializing anything.
class NotificationsMeta(APIView):
def get(self, request, format=None):
qs = Notification.objects.filter(created_for=self.request.user)
response = {
'total': qs.count(),
'read': qs.filter(read=None).count(),
'unread': qs.exclude(read=None).count()
}
return Response(response)
Now remove those property functions from your model.
I didn't test your queries, just copied them from your model. You will need to check if they are working properly. Hope this helps.
I am not sure about how calling a model property who is responsible for querying in model can give appropriate data from serializer. Unfortunately i do have knowledge gap about that. I am thinking about an alternative solution. I hope following should work.
class NotificationSerializer(serializers.ModelSerializer):
total = serializers.serializers.SerializerMethodField()
read = serializers.ReadOnlyField()
unread = serializers.ReadOnlyField()
class Meta:
model = Notification
fields = ['read', 'unread']
def get_total(self, obj):
user = self.context['request'].user
return Notification.objects.filter(created_for=user).count()
If this work then you can able to do similar kind of thing for read and unread too.
In order to get notification for current_user we need to overwrite get_queryset from view.
class NotificationsMeta(generics.ListAPIView):
serializer_class = NotificationSerializer
def get_queryset(self):
return Notification.objects.filter(created_for=self.request.user)

How to save a model in Django Rest Framework having one to one relationship

I have a Django model named BankDetail that has a one to one relationship with User.
#BankeDetails
class BankDetail(models.Model):
account_holder_name = models.CharField(max_length=100)
account_number = models.CharField(max_length=50, blank=True, null=True)
iban = models.CharField("IBAN", max_length=34, blank=True, null=True)
bank_name = models.CharField(max_length=100)
bank_address = models.CharField(max_length=500)
swift_bic_code = models.CharField(max_length=11)
user = models.OneToOneField(MyUser,on_delete=models.CASCADE)
accepting_fiat_currency = models.OneToOneField(AcceptedFiatCurrency)
created_at = models.DateTimeField(auto_now_add=True,null=True)
updated_at = models.DateTimeField(auto_now=True)
The serializer is as listed below :
class BankDetailSerializer(serializers.ModelSerializer):
"""Serializer for BankDetails"""
class Meta:
model = BankDetail
fields = "__all__"
def validate(self, data):
if data['account_number'] or data['iban']:
raise serializers.ValidationError("Please fill Account Number or IBAN")
return data
Request Payload :
{
"account_holder_name":"Aladin",
"account_number":"1239893",
"bank_name":"Aladin Bank",
"bank_address":"Republic of Wadia",
"swift_bic_code":"1",
"user_id":"1",
"accepting_fiat_currency_id":"1"
}
Now when I'm trying to save the model from my view, I get the following error :
{
"user": [
"This field is required."
],
"accepting_fiat_currency": [
"This field is required."
]
}
How can I pass I refrence ob these objects, do I need to manually retrieve them from the db using the id's?
AFAIR you should define a related model field in the serializer. And don't use __all__ instead of explicit write all fields. It's my recommendation :)
You should find answer in following questions:
Post from StackOverflow
That helped me last week
DRF documentation about Serializer relations
One solution is to use different serializer for creating/retrieving data in/from database.
Your serializer for creating should be something like -
class BankDetailSerializer(serializers.ModelSerializer):
"""Serializer for BankDetails"""
class Meta:
model = BankDetail
exclude = ('user', 'accepting_fiat_currency', ) # Note we have excluded the related fields
def validate(self, data):
if data['account_number'] or data['iban']:
raise serializers.ValidationError("Please fill Account Number or IBAN")
return data
Then while saving the serializer pass the above two excluded fields. Like -
serializer = BankDetailSerializer(data=request.data)
if serializer.is_valid():
serializer.save(user=request.user, accepting_fiat_currency=<AcceptedFiatCurrency-object>)
If you are using Generic Views in DRF then I would suggest you to look
into perform_create method.
You need to send "user":"1". instead of "user_id":"1". The same with other "accepting_fiat_currency"

Return annotated queryset against graphql queries on django graphene

class Project(models.Model):
name = models.CharField(max_length=189)
class Customer(models.Model):
name = models.CharField(max_length=189)
is_deleted = models.BooleanField(default=False)
project = models.ForeignKey(Project, related_name="customers")
class Message(models.Model):
message = models.TextField()
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name="messages")
created_at = models.DateTimeField(auto_now_add=True)
I am using the following queryset to get all customers under a certain project ordered by who had messaged last.
qs = Customer.objects.filter(messages__isnull=False) \
.annotate(last_message=Max('messages__created_at')).order_by('-last_message')
Now I want to use a basic graphene query (NOT relay) to get a project and the customers associated with this project according to the annotated queryset.
I may also have a second use-case where I would need to filter the project.customers.all() queryset according to a field in the Customer table (e.g. the customers which have is_deleted=False).
Currently in my graphql schema i have,
class ProjectNode(DjangoObjectType):
class Meta:
model = Project
class CustomerNode(DjangoObjectType):
class Meta:
model = Customer
class Query(graphene.ObjectType):
project = graphene.Field(ProjectNode, id=graphene.Int(), token=graphene.String(), )
top_chat_customers = graphene.Field(CustomerNode, project_id=graphene.Int())
def resolve_project(self, info, **kwargs):
pk = kwargs["id"]
return Project.objects.filter(id=pk).first()
def resolve_top_chat_customers(self, info, **kwargs):
project = Project.objects.filter(id=kwargs["project_id"]).first()
return Customer.objects.filter(project=project, messages__isnull=False) \
.annotate(last_message=Max('messages__created_at')).order_by('-last_message')
Here when I try to get the list of customers separately by providing the project Id, It shows error: "Received incompatible instance..."
Any ideas on how I should get the list of customers from within the project node and as a separate top_chat_customers query?
Any help is appreciated. Thanks!
The method resolve_top_chat_customers returns an iterable queryset, and not an individual Customer object, so in Query you need to specify that you're returning a list:
top_chat_customers = graphene.List(CustomerNode, project_id=graphene.Int())
Also, any annotated fields from the query aren't going to be part of the schema automatically. If you want to see them you'd need to add them explicitly:
class CustomerNode(DjangoObjectType):
last_message = graphene.String()
class Meta:
model = Customer
def resolve_last_message(self, info):
# Returns last message only if the object was annotated
return getattr(self, 'last_message', None)