Django restful create api marks a new entry as edited on creation - python

I have been working on tracking edit history of comments in my app using django-simple-history, I have been able to track the edited comments but just realized that am also tracking entries upon creation. ie new entries are added to the history table on creation.
my model
class Comments(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateField(auto_now=True)
body = models.TextField(max_length=200)
is_Child = models.BooleanField(default=False)
author = models.ForeignKey(User, related_name='author_rel',
on_delete=models.CASCADE)
article = models.ForeignKey(Article, related_name='comments',
on_delete=models.CASCADE, null=False)
parent = models.ForeignKey('self', null=True, blank=True,
on_delete=models.CASCADE, related_name='threads')
history = HistoricalRecords()
def __str__(self):
return str(self.body)
class Meta:
ordering = ['-created_at']
My view class
class CommentsListView(ListCreateAPIView):
serializer_class = CommentSerializer
permission_classes = (IsAuthenticatedOrReadOnly,)
queryset = Comments.objects.all()
renderer_classes = (CommentJSONRenderer,)
def create(self, request, slug, *args, **kwargs):
serializer_context = {
'request': request,
'article': get_object_or_404(Article, slug=self.kwargs["slug"])
}
article = Article.objects.filter(slug=slug).first()
data = request.data
serializer = self.serializer_class(
data=data, context=serializer_context)
serializer.is_valid(raise_exception=True)
serializer.save(author=self.request.user, article_id=article.pk)
return Response(serializer.data, status=status.HTTP_201_CREATED)

That's part of django-simple-history behavior but what you could do is delete that type of records in a signal if you do not want to store that information.
from django.dispatch import receiver
from simple_history.signals import (
post_create_historical_record
)
#receiver(post_create_historical_record)
def post_create_historical_record_callback(sender, **kwargs):
history_instance = kwargs['history_instance']
if history_instance.get_history_type_display() == "Created":
history_instance.delete()

Related

drf: serializers.ModelField > How can i get user_id appropriately?

I want to implement the 'user_id' to be automatically saved at the backend without receiving it from the client. (when create object!)
This is my code.
models.py
class User(AbstractUser):
username = None
email = models.EmailField(max_length=255, unique=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = UserManager()
social_profile = models.URLField(null=True,blank=True)
realname = models.CharField(max_length=50, blank=True)
nickname = models.CharField(max_length=50, null=True, unique=True)
address = models.CharField(max_length=200, blank=True)
phone = models.CharField(max_length=100, blank=True)
def __str__(self):
return self.email
class Item(models.Model):
user_id = models.ForeignKey(User, related_name='item_sets', on_delete=models.CASCADE)
category_id = models.ForeignKey(Category, related_name='item_sets', on_delete=models.DO_NOTHING)
description = models.TextField()
feature = models.TextField()
product_defect = models.TextField()
size = models.CharField(max_length=6)
height = models.DecimalField(max_digits=4, decimal_places=1, default=0)
weight = models.DecimalField(max_digits=4, decimal_places=1, default=0)
condition = models.CharField(max_length=20)
price = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
sold_out = models.BooleanField(default=False)
def __str__(self):
return self.description
view.py
class ItemViewSet(ModelViewSet):
queryset = Item.objects.all()
serializer_class = ItemSerializer
filter_backends = [SearchFilter, OrderingFilter]
search_fields = ['description'] # ?search=
ordering_fields = ['created_at'] # ?ordering=
ordering = ['-created_at']
authentication_classes = (JWTCookieAuthentication,)
# create
def create(self, request, *args, **kwargs):
city = request.data['city']
gu = request.data['gu']
dong = request.data['dong']
if city is not None and gu is not None and dong is not None:
location = Location.objects.get(
Q(city=city) & Q(gu=gu) & Q(dong=dong)
)
else:
return Response(
{"message": "주소정보를 모두 입력해주세요."},
status=status.HTTP_400_BAD_REQUEST
)
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
new_item = Item.objects.get(id=serializer.data['id'])
headers = self.get_success_headers(serializer.data)
LocationSet.objects.create(item_id=new_item, location_id=location)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
# here!
def perform_create(self, serializer):
serializer.save(user_id=self.request.user)
serializers.py
class ItemSerializer(serializers.ModelSerializer):
photos = PhotoSerializer(source='photo_sets', many=True, read_only=True)
style_photos = StylePhotoSerializer(source='style_photo_sets', many=True, read_only=True)
user_id = serializers.ModelField(model_field=User()._meta.get_field('id'), required=False) #here!
class Meta:
model = Item
fields = '__all__'
# Photo, StylePhoto
def create(self, validated_data):
images_data = self.context['request'].FILES
item = Item.objects.create(**validated_data)
for photo_data in images_data.getlist('photos_data'):
Photo.objects.create(item_id=item, photo=photo_data)
for style_photo_data in images_data.getlist('style_photos_data'):
StylePhoto.objects.create(item_id=item, user_id=self.context['request'].user,
photo=style_photo_data)
print(User()._meta.get_field('id'))
return item
so I overrided perform_create() in views.py
and write 'user_id = serializers.ModelField(model_field=User()._meta.get_field('id'), required=False)' in serializers.py
but it doesn't work well like my intend...
correct user_id is 8. but my object has 18..:<
18 is just item_id, not user_id..
what should I do to fix this error?
Try this:
views.py : use the perform_create() method to save the user_id.
Note: the user must be authenticated so that we can get them from the request.
class ItemViewSet(ModelViewSet):
# your code ...
# create
def create(self, request, *args, **kwargs):
# your code code ...
def perform_create(self, serializer): # new
serializer.save(user_id=self.request.user)
serializers.py :
Get the user_id from the database and make it readonly as it is not supposed to be edited by the user:
class ItemSerializer(serializers.ModelSerializer):
# your code ..
user_id = serializers.ReadOnlyField(source='user_id.id') # new
class Meta:
model = Item
fields = '__all__'
# Photo, StylePhoto
def create(self, validated_data):
images_data = self.context['request'].FILES
item = Item.objects.create(**validated_data)
for photo_data in images_data.getlist('photos_data'):
Photo.objects.create(item_id=item, photo=photo_data)
for style_photo_data in images_data.getlist('style_photos_data'):
StylePhoto.objects.create(item_id=item,photo=style_photo_data) # new
print(User()._meta.get_field('id'))
return item
I would also recommend you change the user_id field to something like owner to prevent future confusion.

I want to use ForeignKey Serializer

I have these Models Below
class Campaign(models.Model):
title = models.CharField(max_length=120)
img = models.ImageField(upload_to='products/')
def __str__(self):
return self.title
class Product(models.Model):
title = models.CharField(max_length=100)
data = models.DateField(auto_now_add=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
image = models.ImageField(upload_to="products/")
campaign = models.ForeignKey(Campaign, on_delete=models.DO_NOTHING, null=True, blank=True, related_name="products")
offer = models.ForeignKey(Offer, on_delete=models.DO_NOTHING, null=True, blank=True)
market_price = models.PositiveIntegerField()
selling_price = models.PositiveIntegerField()
description = models.TextField()
def __str__(self):
return self.title
I want to show campaign_wise products. But i cant find any solution for this. Though i tried reading the docs of DRF
Here is my Serializer
class CampaignProductsSerializer(serializers.ModelSerializer):
products = serializers.PrimaryKeyRelatedField(read_only=True, many=True)
class Meta:
model = Campaign
fields = ['title', 'products']
Whenever i try to run this i get 'Product' object has no attribute 'products'
Here is my URL
path('campaign_products/<int:id>/', CampaignProducts.as_view()),
Here is my View:
class CampaignProducts(APIView):
def get(self, request, id):
campaigns = Campaign.objects.all()
query = Product.objects.filter(campaign_id = id)
serializer = CampaignProductsSerializer(query, many=True)
return Response(serializer.data)
In that case you should make two serializers, one for the Product and one for the Campaign, so:
class ProductSerializer(serializers.ModelSerializer):
class Meta:
model = Product
fields = ['title'] # etc.
class CampaignProductsSerializer(serializers.ModelSerializer):
products = ProductSerializer(read_only=True, many=True)
class Meta:
model = Campaign
fields = ['title', 'products']
In the view, you then serializer a Campaign:
from django.shortcuts import get_object_or_404
# returns the title of the campaign, and the products
class CampaignProducts(APIView):
def get(self, request, id):
campaign = get_object_or_404(Campaign, id=id)
serializer = CampaignProductsSerializer(campaign)
return Response(serializer.data)
In case you do not want to include the data of the campaign, we can work with the ProductSerializer:
# returns only the related products
class CampaignProducts(APIView):
def get(self, request, id):
products = Product.objects.filter(campaign_id=id)
serializer = ProductSerializer(products, many=True)
return Response({'data': serializer.data})

AttributeError at /apiv2/api/processorder/order/

I am trying to post multiple data into my DataBase Using Django Rest framework (DRF).
AttributeError at /apiv2/api/processorder/order/
Got AttributeError when attempting to get a value for field subcategory on serializer MyProcessOrderSerializer.
The serializer field might be named incorrectly and not match any attribute or key on the list instance.
Original exception text was: 'list' object has no attribute 'subcategory'.
models.py
class SubCategory(models.Model):
category = models.ForeignKey(Category, related_name='subcategory', on_delete=models.CASCADE)
name = models.CharField("Food Name", max_length=50, help_text="Name of The Food")
price = models.DecimalField("Food Price", max_digits=5, decimal_places=2)
quantity = models.PositiveIntegerField("Qty.", help_text="Quantity of the food Item you want")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f'{self.name}'
class Meta:
verbose_name = 'SubCategory'
verbose_name_plural = 'SubCategories'
class Order(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
bike = models.ForeignKey(User, related_name='bike', on_delete=models.CASCADE, blank=True, null=True)
package = models.ForeignKey(PackageType, related_name='package', on_delete=models.CASCADE, blank=True, null=True)
total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0000.0)
qty = models.PositiveIntegerField(default=1)
shipping_address = models.CharField("Delivery Address", max_length=150)
paid = models.BooleanField(default=False)
ordernote = models.TextField("Order Notes", null=True)
shipped = models.BooleanField(default=False)
complete = models.BooleanField(default=False)
received = models.BooleanField(default=False)
refund_requested = models.BooleanField(default=False)
refund_granted = models.BooleanField(default=False)
ref_code = models.CharField(max_length=20, blank=True, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ('-created_at',)
def __str__(self):
return '{}'.format(self.id)
def order(self):
if not hasattr(self, '_order'):
self._order = self.order.all()
return self._order
'''
def get_total_cost(self):
total_cost = sum(orders.get_cost() for orders in self.order.all())
return total_cost
'''
class ProcessOrder(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
order = models.ForeignKey(Order, related_name='order', on_delete=models.CASCADE)
quantity = models.PositiveIntegerField("Qty.", default=1, help_text="Quantity of the food Item you want")
#category = models.ForeignKey(Category, related_name='category', on_delete=models.CASCADE)
subcategory = models.ForeignKey(SubCategory, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return f'{self.order} -- {self.subcategory.name}'
serializers.py
class MyProcessOrderSerializer(serializers.ModelSerializer):
#subcategory_name = serializers.RelatedField(source='subcategory.id', read_only=True)
#subcategory_set = SubCategoryOrderSerializer(many=True)
class Meta:
model = ProcessOrder
fields = ('quantity', 'subcategory', 'user')
read_only_fields = ('user', )
def create(self, validated_data):
return ProcessOrder.objects.create(**validated_data)
view.pf
#api_view(['POST'])
#permission_classes([IsAuthenticated])
def processorder_view(request):
orderuser = User.objects.get(id=request.user.id)
serializer = MyProcessOrderSerializer(data=request.data, many=True)
if serializer.is_valid():
order = Order.objects.create(user=orderuser, ref_code=create_ref_code())
order.save()
processorder = serializer.save(order=order, user=orderuser)
return Response(MyProcessOrderSerializer(processorder).data, status=status.HTTP_201_CREATED)
else: #return Response("Process Order Created Successfully")
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I am now trying to make a POST such as this but getting the above error.
[{
"quantity": 16,
"subcategory": 1
},
{
"quantity": 14,
"subcategory": 3
}
]
You have to explicitly specify the relation in the serializer either with one of the built-in serializer:
Serializer relations
class MyProcessOrderSerializer(serializers.ModelSerializer):
# StringRelatedField and PrimaryKeyRelatedField are some of the built in ones.
subcategory = serializers.StringRelatedField(source='subcategory.id', read_only=True)
user = serializer.PrimaryKeyRelatedField()
class Meta:
model = ProcessOrder
fields = ('quantity', 'subcategory', 'user')
read_only_fields = ('user', )
def create(self, validated_data):
# If you want to accect new subcategory object via the endpoint you need further action here, see the docs.
return ProcessOrder.objects.create(**validated_data)
or by creating a custom subcategory serializer and then in serializer:
class UserSerializer(serializers.ModelSerializer):
class Meta:
fields = ('id', 'email', 'password', 'first_name', 'last_name')
class SubCategorySerializer(serializers.ModelSerializer):
class Meta:
fields = # whatever fields youd'e like to include
class MyProcessOrderSerializer(serializers.ModelSerializer):
# StringRelatedField is One of the built in ones.
sybcategory = SubCategorySerializer()
user = UserSerializer()
class Meta:
model = ProcessOrder
fields = ('quantity', 'subcategory', 'user')
read_only_fields = ('user', )
def create(self, validated_data):
# If you want to accect new subcategory object via the endpoint you need further action here, see the docs.
return ProcessOrder.objects.create(**validated_data)
And, I noticed you have Foreign Key to User in process order and order itself,
You could delete the user field from the process order and access it via the order relation, for that you can specify the user field in the subcategory serializer and the subcategory serializer in the process order serializer as I showed above.
And if I understand the purpose of creating a process order model, the relation should be a One to One and not Foreign key, But maybe I'm not seeing the whole picture here.
I think the problem apear in request.data in this line :
serializer = MyProcessOrderSerializer(data=request.data, many=True)
You should loop throught your object list and pass just the object not the whole list .
exemple :
{ "list_":[{"quantity": 7 ,"subcategory": 3}, {"quantity": 7 ,"subcategory": 3}] }
To get the first object from the request :
request.data.get('list_')[0]
I was able to solve the problem with this code both in my views and serialize
views.py
#api_view(['POST'])
#permission_classes([IsAuthenticated])
def processorder_view(request):
orderuser = User.objects.get(id=request.user.id)
data = request.data
order = Order.objects.create(user=orderuser, ref_code=create_ref_code())
if isinstance(data, list):
serializer = MyProcessOrderSerializer(data=request.data, many=True)
else:
serializer = MyProcessOrderSerializer(data=request.data)
if serializer.is_valid():
processorder = serializer.save(order=order, user=request.user)
return Response(status=status.HTTP_201_CREATED)
#return HttpResponse("Question created", status=201)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
serializers.py
class MyProcessOrderSerializer(serializers.ModelSerializer):
#subcategory_name = serializers.RelatedField(source='subcategory.id', read_only=True)
#subcategory_set = SubCategoryOrderSerializer(many=True)
#subcategory = serializers.StringRelatedField(source='subcategory.id', read_only=True)
class Meta:
model = ProcessOrder
fields = ('quantity', 'subcategory', 'user')
read_only_fields = ('user', )
def create(self, validated_data):
return ProcessOrder.objects.create(**validated_data)
Then, I was able to send multiple data with this sample.
[
{
"subcategory": 1,
"quantity": 12
},
{
"subcategory": 3,
"quantity": 12
}
]

Creating a nested class in django and running a helper method

I've the following User model,
class User(AbstractBaseUser, PermissionsMixin, Base):
username = models.CharField(max_length=255, null=False)
email = models.CharField(max_length=255, null=False, unique=True)
user_type = models.CharField(max_length=255, null=False)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
And this is my Meeting model
class Meeting(Base):
meeting_code = models.CharField(max_length=255)
owner = models.ForeignKey(User, related_name='meetings', on_delete=models.CASCADE, null=True, blank=True)
members = models.ManyToManyField(User, related_name='meeting_set', null=True, blank=True)
start_time = models.DateTimeField(auto_now_add=True)
end_time = models.DateTimeField(null=True)
When a meeting is created, I want to run this helper function to generate a meeting code
def generate_meetingid():
return ''.join(random.choices(string.ascii_uppercase, k=16))
This is my meeting serializer,
class MeetingSerializer(serializers.ModelSerializer):
owner = UserSerializer(required=True)
class Meta:
model = Meeting
fields = ['id', 'meeting_code', 'owner', 'members', 'start_time', 'end_time', ]
My questions is how do I write the Meeting View set that adds the creating user as the owner and also runs the helper method to create the meeting code.
In essence, I'm trying to complete this view,
class MeetingViewSet(viewsets.ModelViewSet):
queryset = Meeting.objects.all()
serializer_class = MeetingSerializer
def perform_create(self, serializer):
serializer.save()
The easiest way is to override the save method, while I usually prefer using pre_save signal. The logic is similar to generating a unique slug for model instance, the helper function is automatically invoked before you save the object. In your serializer you can simply mark meeting_code as read_only so the meeting_code is not used when creating or updating the instance during deserialization
# override save method
class Meeting(models.Model):
...
def save(self, *args, **kwargs):
# avoid meeting_code IntegrityError
# self._state.adding == True means item was not added in the database yet
if self._state.adding and not self.meeting_code:
self.meeting_code = generate_meetingid()
while Meeting.objects.filter(meeting_code=self.meeting_code).exists():
self.meeting_code = generate_meetingid()
super().save(*args, **kwargs)
# pre_save signal
#receiver(pre_save, sender=Meeting)
def generate_meeting_code(sender, **kwargs):
if kwargs.get('created', False):
# on meeting create
meeting=kwargs.get('instance')
meeting.meeting_code = generate_meetingid()
while Meeting.objects.filter(meeting_code=self.meeting_code).exists():
meeting.meeting_code = generate_meetingid()
Serializer
class MeetingSerializer(serializers.ModelSerializer):
owner = UserSerializer(required=True)
class Meta:
model = Meeting
fields = ['id', 'meeting_code', 'owner', 'members', 'start_time', 'end_time', ]
read_only_fields = ['meeting_code']
Views
class MeetingViewSet(viewsets.ModelViewSet):
queryset = Meeting.objects.all()
serializer_class = MeetingSerializer
def perform_create(self, serializer):
# pass owner to the serializer
serializer.save(owner=self.request.user)

Django DRF serializer on PrimaryKeyRelatedField

Question about PrimaryKeyRelatedField serialization in Django DRF version 3.4.7.
models
class UserModel(AbstractEmailUser):
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return str(self.email)
class Conversation(models.Model):
admin = models.ForeignKey('UserModel', db_index=True, related_name='admin_user')
patient = models.ForeignKey('UserModel', db_index=True)
subject = models.CharField(max_length=255, db_index=True)
user_reply = models.BooleanField(default=False)
admin_seen = models.BooleanField(default=False)
expire = models.DateTimeField()
conversation_type = models.ForeignKey('ConversationType', db_index=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return str(self.admin)
class ConversationMessages(models.Model):
text = models.TextField(db_index=True)
conversation = models.ForeignKey('Conversation', db_index=True, related_name='msg_conv')
user = models.ForeignKey('UserModel', db_index=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return str(self.user)
class ConversationFiles(models.Model):
message = models.ForeignKey('ConversationMessages', db_index=True, related_name='message')
file = models.FileField(upload_to='conversations', db_index=True)
def __str__(self):
return str(self.user)
Every model has related field for Rest Framework.
Logic is create conversation, then take ID from conversation and save message model.
serialize's
class MessagesSerializer(serializers.ModelSerializer):
text = serializers.CharField(required=False)
conversation = serializers.PrimaryKeyRelatedField(queryset=Conversation.objects.all(), required=False)
user = serializers.PrimaryKeyRelatedField(queryset=UserModel.objects.all(), required=False)
class Meta:
model = ConversationMessages
class ConversationSerializer(serializers.ModelSerializer):
admin = serializers.PrimaryKeyRelatedField(queryset=UserModel.objects.all(), required=False)
msg_conv = MessagesSerializer()
class Meta:
model = Conversation
def create(self, validated_data):
msg_conv = validated_data.pop('msg_conv', None)
admin_user = Conversation.objects.create(**validated_data)
ConversationMessages.objects.create(conversation_id=Conversation.objects.get(id=admin_user.id).id, **msg_conv)
return admin_user
Serializer is problem on POST method. Everything works great POST object create data in database, but problem is when serializer save object i get this message: 'RelatedManager' object has no attribute 'conversation'.
View
class ConversationView(APIView):
authentication_classes = (JSONWebTokenAuthentication,)
#parser_classes((FormParser, MultiPartParser, FileUploadParser))
def post(self, request):
admin = request.user.id
data = request.data
my_time = datetime.datetime.strptime('07/05/15', '%m/%d/%y')
my_time = my_time.replace(hour=23, minute=59)
data['admin'] = admin
data['expire'] = my_time
data['msg_conv']['user'] = admin
serializer = ConversationSerializer(data=data)
if serializer.is_valid():
serializer.save()
return Response(data={'success': True, 'user': serializer.data}, status=status.HTTP_200_OK)
else:
return Response(data={'success': False, 'msg': serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
POST json
{
"subject":"aloha",
"conversation_type":1,
"patient":3,
"msg_conv":{
"text":"ovo je neki teks"
}
}
Can't figure out how to return data from serializer to view.
Django version : 1.10.2
Python: 3.4.3
The issue is there:
class ConversationSerializer(serializers.ModelSerializer):
msg_conv = MessagesSerializer()
By doing this, you are saying that Conversation has a FK to Message. Therefore DRF tries to do the mapping and fails because it's the oposit.
You just need to add the many=True argument to let DRF knows this is a reversed FK.

Categories

Resources