Import column from another table - django

I made API with Django Restframework.
[models.py]
from django.db import models
class Article(models.Model):
article_no = models.AutoField(primary_key=True)
content = models.CharField(max_length=500, null=False)
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
class Comment(models.Model):
article_no = models.ForeignKey('Article', on_delete=models.CASCADE)
content = models.CharField(max_length=50, null=False, default='')
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
[views.py]
class ArticleDetail(APIView):
def get(self, request, article_no, format=None):
try:
article = models.Article.objects.get(article_no=article_no)
serializer = serializers.ArticleDetailSerializer(article)
return Response(status=status.HTTP_200_OK, data=serializer.data)
except models.Article.DoesNotExist:
return Response(status=status.HTTP_404_NOT_FOUND)
[urls.py]
urlpatterns = [
path('article/<int:article_no>', views.ArticleDetail.as_view(), name='article_detail'),
]
[serializers.py]
class ArticleDetailSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
comment = CommentSerializer(many=True, read_only=True)
class Meta:
model = models.Article
fields = (
'article_no',
'content',
'password',
'date',
'comment',
)
In serializers.py, I defined comment = CommentSerializer(many=True, read_only=True) and add it to fields.
And to test it, I add comment for article_no=1
But When I connect to /article/1, comment doesn't show anything.
I want to show all comments related it's article_no.
How can I fixed it?
Thanks.
Fixed source is here.
[serializers.py]
class ArticleDetailSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True)
class Meta:
model = models.Article
fields = (
'article_no',
'content',
'password',
'date',
'comments',
)
[models.py]
class Comment(models.Model):
article_no = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments')
content = models.CharField(max_length=50, null=False, default='')
password = models.CharField(max_length=20, null=False, default='1234')
date = models.DateTimeField(auto_now_add=True)
When I connect to my server,
It only shows related comment's article_no.
But I want to show content and date also.

According to the Django documentation at https://docs.djangoproject.com/en/2.0/topics/db/queries/#backwards-related-objects you can access the list of objects by calling article_instance.comment_set.all() or you could set the related_name argument on the model on initialization
article_no = models.ForeignKey('Article', on_delete=models.CASCADE, related_name="comments")
and access is like article_instance.comments.all() or filter() or exclude()
There are quite a few options actually, and it depends on the use case, but for simplicity, in this case you may be able to just change the comment variable to comment_set, or you could change the related_name to comments and refer to it as such in your serializer.
required changes to ArticleDetailSerializer...
comment = CommentSerializer(many=True, read_only=True)
to
comments = CommentSerializer(many=True, read_only=True)
You also haven't created a CommentSerializer class, or you haven't posted it to the question.
example CommentSerializer....
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model=Comment
exclude=('article_no',)
I exclude the article_no and the remaining fields should be handled due to the assigned defaults on the django models.
The related object manager in Django returns a queryset that can be acted on like any other queryset. So you will want to consider whether an article might have an absurd amount of comments and limit the returned amount.
you can also use a SerializerMethodField and have more control over the returned queryset
comments = serializers.SerializerMethodField()
def get_comments(self, obj):
comments = obj.comments/comment_set.all()[:20] #return the first 20 comments
return CommentSerializer(comments/comment_set, many=True, read_only=True).data
now add comments/comment_set to the class Meta/fields tuple

Related

Django: how to include missing pk field into serializer when updating nested object?

I have a serializer in my Django app that is meant for updating a nested object. Updating works, but I'm facing another problem: I can't delete objects that are not in validated_data['events] because I don't have the id to be compared with my instance id's.
For reference, these are my Models:
class Plan(models.Model):
planId = models.CharField(primary_key=True, max_length=100, unique=True)
name = models.CharField(max_length=200)
class PlanEvent(models.Model):
plan = models.ForeignKey(Plan, on_delete=models.CASCADE)
id = models.CharField(primary_key=True, max_length=100, unique=True, blank=False, null=False)
done = models.BooleanField()
title = models.CharField(max_length=100, blank=True)
This is my PlanEventUpdateSerializer:
class PlanEventUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = PlanEvent
fields = ('done', 'title')
Is there some way to include the id, so I could compare the id's like this in my update method:
class PlanUpdateSerializer(serializers.ModelSerializer):
events = PlanEventUpdateSerializer(many=True)
class Meta:
model = Plan
fields = ('name',)
....
def update(self, instance, validated_data):
events_validated_data = validated_data.pop('events')
events = (instance.events.all())
events = list(events)
event_ids = [item['id'] for item in events_validated_data]
for event in events:
if event.id not in event_ids:
event.delete()
I found a solution. I defined the id as a optional field in the serializer and then I was able to include it in the fields. Sending POST and PUT requests works now and I'm also able to delete objects when updating:
class PlanEventUpdateSerializer(serializers.ModelSerializer):
id = serializers.CharField(source='pk', required=False)
class Meta:
model = PlanEvent
fields = ('id', 'done', 'title')

Django, DRF (& React) Foreign key, how to display the name value instead of the foreign key id

There are two Django models - ClientCompany & Proposal and the foreign key of ClientCompany is within the Proposal model. In Proposal how do I display the name of the ClientCompany instead of the foreign key id?
models.py:
class ClientCompany(models.Model):
name = models.CharField("Client Name", max_length=255)
abn_acn = models.BigIntegerField("ABN / ACN")
def __str__(self):
return self.name
class Proposal(models.Model):
proj_name = models.CharField("Project Name", max_length=255)
loc_state = models.CharField(
max_length=3,
)
proj_type = models.CharField(
max_length=30,
)
prop_status = models.CharField(
max_length=20,
)
client = models.ForeignKey(ClientCompany, on_delete=models.CASCADE)
oneic = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='main_engineer')
twoic = models.ForeignKey(
User, on_delete=models.CASCADE, related_name='second_engineer')
created_at = models.DateTimeField(default=datetime.now)
def __str__(self):
return self.proj_name
serializers.py:
class ClientSerializer(serializers.ModelSerializer):
class Meta:
model = ClientCompany
fields = ('id', 'name', 'abn_acn')
class ProposalSerializer(serializers.ModelSerializer):
class Meta:
model = Proposal
fields = ('id', 'proj_name', 'loc_state', 'proj_type', 'prop_status', 'client', 'oneic', 'twoic',)
queryset api.py:
class ProposalViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.IsAuthenticated, ]
queryset = Proposal.objects.all()
serializer_class = ProposalSerializer
currentlyshows the client foreign key id
I've been stuck on this, tried to apply the existing solutions recommended for similar problems but had no luck... if someone can tell me what I'm missing - thanks
I found this worked in the end by adding the serializers.SlugRelatedField line in serializers.py:
class ProposalSerializer(serializers.ModelSerializer):
client = serializers.SlugRelatedField(slug_field="name", read_only=True)
class Meta:
model = Proposal
fields = ('id', 'proj_name', 'loc_state', 'proj_type', 'prop_status',
'client', 'oneic', 'twoic',)
Update your serializer like this:
class ProposalSerializer(serializers.ModelSerializer):
client = ClientSerializer()
class Meta:
model = Proposal
fields = ('id', 'proj_name', 'loc_state', 'proj_type', 'prop_status', 'client', 'oneic', 'twoic',)

Django Rest Framework: field name 'likes' is not valid for model 'userPost' improperlyConfigured

In my Django Rest Framework api I am attempting to add a property to my model UserPosts that returns all likes for said post. Despite my best efforts I keep running into this error. Here is my post model below:
class UserPosts(models.Model):
userProfile = models.ForeignKey(UserProfile, related_name="posts", on_delete=models.CASCADE)
image = models.ImageField()
caption = models.CharField(max_length=240)
#property
def get_likes(self):
from liked.models import Like
return Like(post=self)
and here is my like model:
class Like(models.Model):
user = models.OneToOneField(UserProfile, on_delete=models.CASCADE,)
post = models.ForeignKey(UserPosts, on_delete=models.CASCADE)
liked_at = models.DateTimeField()
and lastly the post serializer:
class postSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = models.UserPosts
fields = ('userProfile', 'image', 'caption', 'likes')
Thanks.
You have at least three ways. First as #WillemVanOnsem said, by the many_to_one change the likes to like_set
class postSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = models.UserPosts
fields = ('userProfile', 'image', 'caption', 'like_set')
# ^^^^^
Second rename your model's property to likes and fix the queryset inside it
class UserPosts(models.Model):
userProfile = models.ForeignKey(UserProfile, related_name="posts", on_delete=models.CASCADE)
image = models.ImageField()
caption = models.CharField(max_length=240)
#property
def likes(self):
# ^^^^^
from liked.models import Like
return Like.objects.filter(post=self).values() or []
# solution you try ^^^^^^^
return self.like_set.values() or []
# more django way
And Third, the most simple and elegant way for me, is to remove your get_likes and add the related_name to the post ForeignKey:
class Like(models.Model):
user = models.OneToOneField(UserProfile, on_delete=models.CASCADE,)
post = models.ForeignKey(UserPosts, related_name='likes', on_delete=models.CASCADE)
# ^^^^^^^^^^^
liked_at = models.DateTimeField()

Filter on nested serializer in django rest framework

I'm building a serializer in django using the django rest framework. I need to filter the query set for a nested model.
I found How do you filter a nested serializer in Django Rest Framework?, which seemed to have the answer, but when I implemented it there was no change in my data. The only difference I can see is that the serializer referencing the filtered list serializer has other fields as well.
The models (abbreviated for clarity):
class GCUser(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
email = models.EmailField()
is_member = models.BooleanField(default=False)
age = models.SmallIntegerField(blank=True, null=True)
session_key = models.CharField(max_length=100, db_index=True, blank=True, null=True)
class Connection(models.Model):
creation_date = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(GCUser, related_name='user_connection')
event = models.ForeignKey(Event, related_name='event_connection')
role = models.CharField(max_length=8, choices=constants.Roles.ROLE_CHOICES,)
class Event(Game):
creation_date = models.DateTimeField(auto_now_add=True)
public = models.BooleanField(default=False)
start_time = models.DateTimeField(null=True, blank=True)
end_time = models.DateTimeField(null=True, blank=True)
gm_is_player = models.BooleanField(default=False,
help_text='Check if GM will be playing the game',
verbose_name='GM is a player')
gm_is_designer = models.BooleanField(default=False, help_text='Check if GM designed the game')
user_notes = models.TextField(blank=True, default='', verbose_name='Note to Scheduler')
scheduler_notes = models.TextField(blank=True, default='')
experience = models.CharField(max_length=3, choices=constants.ExpLevels.EXPERIENCE_CHOICES,
default=constants.ExpLevels.NOVICE,)
status = models.CharField(max_length=4, db_index=True,
choices=constants.Status.STATUS_CHOICES,)
Here's my code:
class FilteredListSerializer(serializers.ListSerializer):
def to_representation(self, data):
data = data.filter(status=constants.Status.ASSIGNED).order_by('start_time')
return super(FilteredListSerializer, self).to_representation(data)
class UserEventSerializer(serializers.ModelSerializer):
class Meta:
list_serializer_class = FilteredListSerializer
model = models.Event
fields = ('id', 'event_name', 'conflict_type', 'start_time', 'end_time')
class UserConnectionSerializer(serializers.ModelSerializer):
event = UserEventSerializer()
class Meta:
model = models.Connection
fields = ('get_role_display', 'conflict_type', 'event')
class GCUserSerializer(serializers.ModelSerializer):
user_connection = UserConnectionSerializer(many=True)
class Meta:
model = models.GCUser
fields = ('pk', 'first_name', 'last_name', 'email',
'is_member', 'age', 'user_connection')
PyCharm tells me that "class FilteredListSerializer must implement all abstract methods" but it doesn't actually throw an error. I put a breakpoint at the first line of the list serializer, but it doesn't get tripped.
I'm using Python 3.4 with django 1.7.
Thanks in advance for your help.
Edited to add: Looking into the serializer code, I realized what may be the key difference: my call has many=True, whereas the one from the previous post didn't. I tried taking out the model=, but as expected that threw an error, so apparently the "working" code in the earlier post didn't actually run as written.
So I am not sure how to use the method you are using, but, if I understand your question correctly, I believe you could do something like this:
class UserEventSerializer(serializers.ModelSerializer):
class Meta:
model = models.Event
fields = ('id', 'event_name', 'conflict_type', 'start_time', 'end_time')
class UserConnectionSerializer(serializers.ModelSerializer):
event = serializers.SerializerMethodField()
class Meta:
model = models.Connection
fields = ('get_role_display', 'conflict_type', 'event')
def get_event(self, obj):
if obj.event.status == constants.Status.ASSIGNED:
serializer = UserEventSerializer(obj.event)
return serializer.data
else:
serializer = UserEventSerializer(None)
return serializer.data
N.B. This assumes that you are trying to exclude Events from being serialized if their status is not assigned.
I hope this helps. If I didn't understand the problem, let me know.
it's not a bug or error. ModelSerializer has got already implemented all needed methods (most inhereted from Serializer class), but ListSerializer inherits from BaseSerializer and got implemented e.g.: .create() or .to_representation(), but not .update(). I've got some similar problems in PyCharm, when subclassing a Serializer. After implementing create, update and to_representation methods this issue was gone

Limit values in the modelformset field

I've been trying to solve this problem for a couple of days now, getting quite desperate. See the commented out code snippets for some of the things I've tried but didn't work.
Problem: How can I limit the values in the category field of the IngredientForm to only those belonging to the currently logged in user?
views.py
#login_required
def apphome(request):
IngrFormSet = modelformset_factory(Ingredient, extra=1, fields=('name', 'category'))
# Attempt #1 (not working; error: 'IngredientFormFormSet' object has no attribute 'fields')
# ingrformset = IngrFormSet(prefix='ingr', queryset=Ingredient.objects.none())
# ingrformset.fields['category'].queryset = Category.objects.filter(user=request.user)
# Attempt #2 (doesn't work)
# ingrformset = IngrFormSet(prefix='ingr', queryset=Ingredient.objects.filter(category__user_id = request.user.id))
models.py:
class Category(models.Model):
name = models.CharField(max_length=30, unique=True)
user = models.ForeignKey(User, null=True, blank=True)
class Ingredient(models.Model):
name = models.CharField(max_length=30, unique=True)
user = models.ForeignKey(User, null=True, blank=True)
category = models.ForeignKey(Category, null=True, blank=True)
counter = models.IntegerField(default=0)
forms.py:
class IngredientForm(ModelForm):
class Meta:
model = Ingredient
fields = ('name', 'category')
UPDATE: I've made some progress but the solution is currently hard-coded and not really usable:
I found out I can control the categoryform field via form class and then pass the form in the view like this:
#forms.py
class IngredientForm(ModelForm):
category = forms.ModelChoiceField(queryset = Category.objects.filter(user_id = 1))
class Meta:
model = Ingredient
fields = ('name', 'category')
#views.py
IngrFormSet = modelformset_factory(Ingredient, form = IngredientForm, extra=1, fields=('name', 'category'))
The above produces the result I need but obviously the user is hardcoded. I need it to be dynamic (i.e. current user). I tried some solutions for accessing the request.user in forms.py but those didn't work.
Any ideas how to move forward?
You don't need any kind of custom forms. You can change the queryset of category field as:
IngrFormSet = modelformset_factory(Ingredient, extra=1, fields=('name', 'category'))
IngrFormSet.form.base_fields['category'].queryset = Category.objects.filter(user__id=request.user.id)
Category.objects.filter(user=request.user)
returns a list object for the initial value in your form which makes little sense.
Try instead
Category.objects.get(user=request.user)
or
Category.objects.filter(user=request.user)[0]