Change model field if requested particular url - django-rest-framework

Im making API using django rest framework . I only want to change one field in model which is the read field if i go to a particular url
my model:
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)
The APis i made lists the notifications for a logged in user.
What i want to implement is that :
notification/<:id>/markread
notification/<:id>/markunread
If i go to this particular url i want to modify the read field ..For example make it None if to mark unread. Also i need to check if the logged in user has received the notification with that id.I know the basics and how to create the urls
class NotificationMarkRead(generics.UpdateAPIView):
serializer_class = NotificationSerializer
def get_queryset(self):
queryset = Notification.objects.filter(created_for=self.request.user)
return queryset
class NotificationMarkUnread(generics.UpdateAPIView):
serializer_class = NotificationSerializer
def get_queryset(self):
queryset = Notification.objects.filter(created_for=self.request.user)
return queryset
def update
My initial try is to override the put method in update_API view

Write a simple function:
#api_view(['PUT'])
def notification_toggle_read_status(request, pk, read_status):
notification = Notification.objects.get(pk=pk)
if read_status == 'markread':
notification.read = timezone.now()
else:
notification.read = None
notification.save(update_fields=['read'])
serializer = NotificationSerializer(instance=notification)
return Response(serializer.data)
use this url path:
notifications/<int:pk>/<string:read_status>/

As you have already coding with DRF why not try with viewset link . And from front-end just pass update fields with put request.

Related

AttributeError: 'collections.OrderedDict' object has no attribute 'pk' with Django Rest Framework while adding item to a many to many relationship

First question here, so please bear with me. I'm learning Django and the Django Rest Framework, and I'm getting an exception when adding a related object in a many to many relationship. What I do not understand is that the object gets created correctly, and I am getting the error when getting the response.
This is an excerpt of my urls.py:
path('mybox/', views_api.MyBoxList.as_view(), name='mybox-list'),
path('mybox/<int:pk>/', views_api.MyBoxDetail.as_view(), name='mybox-detail'),
path('mybox/<int:pk>/documents/', views_api.DocumentList.as_view(), name='mybox-document-list'),
path('documents/', views_api.DocumentList.as_view(), name='document-list'),
path('documents/<int:pk>/', views_api.DocumentDetail.as_view(), name='document-detail'),
My models.py:
class MyBox(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='boxes')
name = models.CharField(_('name'), max_length=100, default='my_box')
description = models.CharField(_('description'), max_length=250, null=True, blank=True)
time_created = models.DateTimeField(_('time created'), auto_now_add=True)
def __str__(self):
return self.name
def user_folder_doc_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/user_<id>/docs/<filename>
return 'user_{0}/docs/{1}'.format(instance.owner.id, filename)
class Document(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='documents')
file = models.FileField(_('file'), upload_to=user_folder_doc_path, null=True, blank=True)
description = models.TextField(_('description'), null=True, blank=True)
time_created = models.DateTimeField(_('time created'), auto_now_add=True)
mybox = models.ManyToManyField(MyBox, related_name='documents', blank=True)
My serializers:
class MyBoxSerializer(serializers.HyperlinkedModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
class Meta:
model = MyBox
fields = ['url', 'id', 'user', 'name', 'description', 'documents']
class DocumentSerializer(serializers.HyperlinkedModelSerializer):
mybox = MyBoxSerializer(many=True, read_only=True)
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Document
fields = ['url', 'id', 'owner', 'description', 'file', 'mybox']
extra_kwargs = {'mybox': {'required': False}}
And my views.py:
class DocumentList(generics.ListCreateAPIView):
#queryset = Document.objects.all()
serializer_class = DocumentSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
"""
This view should return a list of all the documents
for the currently authenticated user.
"""
owner = self.request.user
mybox_id = self.kwargs.get('pk', None)
if mybox_id:
return Document.objects.filter(mybox=mybox_id, owner=owner)
else:
return Document.objects.filter(owner=owner)
def perform_create(self, serializer):
mybox_id = self.kwargs.get('pk', None)
if mybox_id:
print("Hay ID de MyBox")
mybox = MyBox.objects.get(pk=mybox_id)
serializer = DocumentSerializer(data=self.request.data)
if serializer.is_valid():
document = serializer.save(owner=self.request.user)
mybox.documents.add(document)
else:
print("No hay ID de MyBox")
serializer.save(owner=self.request.user)
I've tried several suggestions I found online, with no luck. I believe the issue is my serializers, and I tried adding this to MyBoxSerializer: documents = serializers.HyperlinkedIdentityField(view_name='mybox-document-list', lookup_field='mybox_id')
But I think I do not understand serializers enough when dealing with M2M relationships.
Just to reiterate, after going to mybox/1/documents/ and creating a new document here, the document gets properly created and the relationship with MyBox id:1 (in this example) is also there, but I never get a response, only the exception
Thanks for any help/suggestions!
So I finally managed to fix the issue, thanks to the solution provided by somebody else. In case anyone gets to this page with a similar issue.
The problem was in the perform_create function, which had a serializer too many and I had to save the document too. This is the correct version that works for me:
def perform_create(self, serializer):
mybox_id = self.kwargs.get('pk', None)
if mybox_id:
mybox = MyBox.objects.get(pk=mybox_id)
if serializer.is_valid():
document = serializer.save(owner=self.request.user)
document.mybox.add(mybox)
document.save()
else:
serializer.save(owner=self.request.user)
I hope this helps

DRF - Add User to a model with ManyToManyField

I am trying to implement a feature to my backend and allow the owner of private "Group" to add other users by their usernames instead of ID's and allow them to add their images to FileField only once after they were added to the model. The code I have so far:
models.py
class Group(models.Model):
group_name = models.CharField(max_length=255)
group_text = models.TextField(max_length=360, blank=True)
owner = models.ForeignKey(
settings.AUTH_USER_MODEL, related_name='owner_user', on_delete=models.SET(get_deleted_user), default=1)
created_on = models.DateTimeField(auto_now_add=True, null=True)
shared_to = models.ManyToManyField(UserProfile, blank=True, related_name='shared_to_user', null=True)
def __str__(self):
return self.group_name
def save(self, *args, **kwargs):
super(Group, self).save(*args, **kwargs)
class GroupImage(models.Model):
group_file = models.FileField(blank=True, null=True,
upload_to='media/covers/%Y/%m/%D/')
gallery_group = models.ForeignKey(Group, related_name='images', on_delete=models.CASCADE)
serializers.py
class GroupImageSerializer(serializers.ModelSerializer):
class Meta:
model = models.GroupImage
fields = ('group_file', )
class SharedToSerializer(serializers.ModelSerializer):
class Meta:
model = models.Group
fields = ('shared_to', )
class GroupSerializer(serializers.ModelSerializer):
images = GroupImageSerializer(many=True, read_only=True)
person = SharedToSerializer(many=True, read_only=True)
class Meta:
model = models.Group
fields = ('id', 'group_name', 'group_text', 'person', 'images')
def create(self, validated_data):
images_data = self.context.get('view').request.FILES
owner_id = self.context['request'].user.id
gallery_group = models.Group.objects.create(group_name=validated_data.get('group_name', 'no-
group_name'), group_text=validated_data.get('group_text'), owner_id=1)
for image_data in images_data.values():
models.GroupImage.objects.create(gallery_group=gallery_group,
group_file=image_data)
return gallery_group
views.py
class GroupCreateAPIView(generics.CreateAPIView):
queryset = models.Group.objects.all()
serializer_class = serializers.GroupSerializer
permission_classes = [AllowAny]
So if your only requirement is how to add users by their username and not their id. You should use SlugRelatedField. I also feel your serializer naming convention is quite confusing. Below is the serializer for Group model that can add users to a group.
class GroupSerializer(Serializer):
... other fields here
shared_to = models.SlugRelatedField(queryset = UserProfile.objects.all(), many=True, slug_field="username", allow_empty=True)
So first checkout SlugRelatedField. This basically is used to map to objects using a specific field of that object(username in this case). You will then get all the UserProfile instances in the shared_to field of the validated_data
property of the serializer which you can fetch in create method and add to you group. And then in the file upload api for your group you can check whether this user belongs to the group or not for permission checking.

How to restrict fields when creating post request in DRF?

I am making a POST api using DRF. In that api, I need only few fields(name, size, customer_name, customer_address), but don't require this fields(status, ordered_time) because these fields I want to save these fields in run time as status='open' and ordered_time=DateTimeField.now()
views.py
class PizzaOrderCustomerView(APIView):
def post(self, request):
orders = request.data.get('orders')
# Create an article from the above data
serializer = ArticleSerializer(data=orders)
if serializer.is_valid(raise_exception=True):
article_saved = serializer.save()
return Response({"success": "Article '{}' created successfully".format(article_saved.name)})
models.py
class PizzaOrder(models.Model):
name = models.CharField(max_length=120)
size = models.CharField(max_length=10, choices=SIZE_CHOICE, default='MEDIUM')
customer_name = models.CharField(max_length=120)
customer_address = models.TextField()
ordered_time = models.DateTimeField(default=timezone.now, editable=False)
status = models.CharField(max_length=20, default='open', editable=False)
serializers.py
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = PizzaOrder
# fields = '__all__'
read_only_fields = ('status',)
But when I try to create an order, it needed status and ordered_time also. But it should save at the time of creating order automatically.
Suggest a good way to do it.
from rest_framework import viewsets, mixins
class PizzaViewsets(viewsets.ViewSet, mixins.CreateModelMixin):
model = PizzaOrder
serializer_class = OrderSerializer
queryset = model.objects.all(
serializer, it is always good practise to mention all fields instead of
all
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = PizzaOrder
fields = ('status','ordered_time','name', 'size', 'customer_name', 'customer_address',)
read_only_fields = ('status','ordered_time',)

How to get the id of a current object in Django serializers?

I have a model, 'Project'. The idea is that, a user will log in and create a project. After creating, the user will work on this project at any time. Certain details will be saved to other models where I have written custom functions for it in Serializers.py.
In order solve the idea I have, I need to retrieve the id of the current project that the user is currently working on in Serializers.py. Below is my code:
View.py
class MaterialTagExcelViewSet(FilteredModelViewSet):
queryset = MaterialTagExcel.objects.all()
serializer_class = MaterialTagExcelSerializer
permission_classes = (IsAuthenticated,)
http_method_names = ('get', 'head', 'post', 'options', 'patch')
Serializers.py
class MaterialTagExcelSerializer(BaseSerializer):
class Meta:
fields = "__all__"
model = MaterialTagExcel
def create(self, validated_data):
name = validated_data.get('name') # get current material name
if name is not None:
name_tag = MaterialTagExcel.objects.filter(name=name).first() # filter name to check if it already exists
client = self.context['request'].user.profile.client # get current client details
if name_tag is not None: # if name exists
objects = MaterialExcelClient.objects.filter(client_id=client.id, name_id=name_tag.id)
if objects.count() == 0:
material_excel_client = MaterialExcelClient(client_id=client.id, name_id=name_tag.id)
material_excel_client.save() # get current id and mat id and save to material_client_excel
return MaterialExcelClient.objects.filter(name_id=name_tag.id).order_by('-id')[0]
else:
return MaterialExcelClient.objects.filter(client_id=client.id, name_id=name_tag.id).first()
else:
MaterialTagExcel.objects.create(**validated_data)
MaterialTagExcel.objects.all() # save if material is new and does not exist
# return the id of this newly created material
obj = MaterialTagExcel.objects.filter(name=name).order_by('-id')[0]
# save the id of the newly created material and current client id into material_excel_client
material_excel_client = MaterialExcelClient(client_id=client.id, name_id=obj.id)
material_excel_client.save()
return MaterialExcelClient.objects.filter(name_id=obj.id).order_by('-id')[0]
From above serializer, I am able to get the client.id with the help of CurrentUserDefault. In my table user is related to profile and profile is related to client but not project. I tried to with a custom CurrentProjectDefault, but I didnt succeeded. I tried with many online sources to solve my problem.
Is there any way to get the id of the current object from client ?
I am apologizing in advance if the solution to my problem is very simple.
If you would need some more details, kindly write it in comment.
Thanks in advance.
Models.py
class MaterialTagExcel():
name = models.CharField(max_length=255, verbose_name='name', null=False, blank=False)
def __str__(self):
return "Material %s: %s" % (self.id, self.name)
#classmethod
def get_queryset_for_user(cls, user):
return cls.objects.all()
class Project():
client = models.ForeignKey(Client, related_name='projects', on_delete=models.PROTECT)
name = models.CharField(max_length=255)
class ToDo(BaseModel):
project = models.ForeignKey(Project, related_name='todos', on_delete=models.CASCADE)
owner_client = models.ForeignKey(Client, related_name='todos', on_delete=models.CASCADE)
You wish to retrieve the current project the user is working on. The goal of a REST API is to be stateless, which roughly means that the request contains all the necessary information to perform its action without relying on an external context.
This means that you have to provide the current project id in each of your request.
So, in your example, when you want to POST a new MaterialTagExcel, you'll have to provide the Project. You can modify your serializer like this to do so:
class MaterialTagExcelSerializer(BaseSerializer):
project = serializers.PrimaryKeyRelatedField(write_only=True, queryset=Project.objects.all())
class Meta:
fields = "__all__"
model = MaterialTagExcel
def create(self, validated_data):
name = validated_data.get('name')
project = validated_data.pop('project') # A Project object
Now, when you're doing a request, you'll have to specify the property project with the id. of the project the user has selected in the menu.

Passing argument from view to Custom RelatedField serializer

How can I pass an argument to a serializers.RelatedField class from views.py. I need to pass language_id to query Language.objects model within that RelatedField.
I am not sure if I took a right approach to this issue. What I want to achieve is to present information about genres associated to a movie from database model about depending on the language. The MovieGenre model has genre ID field which I want to replace with actual Genre name.
My serialiser.py
class GenreField(serializers.RelatedField):
def to_representation(self, value, language_id=1):
genre_name = GenresVideo.objects.get(genre_id=value, language_id=language_id)
return genre_name.name
class MovieGenresSerializer(serializers.ModelSerializer):
genre_id = GenreField(read_only=True)
class Meta:
model = MoviesGenres
As you see, here I query Language.objects with default value but I would like to pass it from views (language_id).
My views.py:
class MovieGenresTestViewSet(viewsets.ModelViewSet):
lookup_field = 'movie'
queryset = MoviesGenres.objects.all()
serializer_class = MovieGenresSerializer
def list(self, request, language_pk):
queryset = MoviesGenres.objects.all()
serializer = MovieGenresSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, movie, language_pk):
queryset = MoviesGenres.objects.filter(movie=movie)
serializer = MovieGenresSerializer(queryset, many=True)
return Response(serializer.data)
And my urls.py:
router.register(r'lang', LanguagesViewSet, base_name='lang')
mov_gen = routers.NestedSimpleRouter(router, r'lang', lookup='language')
mov_gen.register(r'mg', MovieGenresTestViewSet, base_name='mg')
url(r'^api/', include(genre_spec.urls))
My models.py
class Languages(models.Model):
name = models.CharField(unique=True, max_length=255)
short_name = models.CharField(unique=True, max_length=4, blank=True, null=True)
active = models.BooleanField(default="")
class Meta:
managed = False
db_table = 'languages'
ordering = ('id',)
class GenresVideo(models.Model):
genre_id = models.IntegerField()
language = models.ForeignKey('Languages')
name = models.CharField(max_length=255, blank=True, null=True)
class Meta:
managed = False
db_table = 'genres_video'
unique_together = (('genre_id', 'language'),)
ordering = ('genre_id',)
class MoviesGenres(models.Model):
movie = models.ForeignKey(Movies)
genre_id = models.IntegerField()
class Meta:
managed = False
db_table = 'movies_genres'
unique_together = (('movie', 'genre_id'),)
Through the urls routes, I can get a correct response from API including the language_id. I just need to pass it to the view somehow.
Thanks a lot for help!
I'll try to answer to your first question, with the easiest implementation possible: SerializerMethodField. Because we will get the language id via the context passed to the serializer, we should either generate the context for the serializer, or let the framework do that for us.
Now to the problem at hand: you aren't filtering the queryset (MoviesGenres) by language per se. Thus, we can avoid overwriting the list and retrieve methods. Nevertheless, the router mechanism will inject in kwargs for the view method the language_pk parameter - that's the parameter that we will retrieve from within the serializer context:
class MovieGenresSerializer(serializers.ModelSerializer):
genre = searializers.SerializerMethodField()
class Meta:
model = MoviesGenres
def get_genre(self, instance):
# get the language id from the view kwargs
language_id = self.context['view'].kwargs['language_pk']
# get the genre
try:
genre_name = GenresVideo.objects.get(genre_id=instance.genre_id, language_id=language_id).name
except GenresVideo.DoesNotExist:
genre_name = None
# return the formatted output
return genre_name
class MovieGenresTestViewSet(viewsets.ModelViewSet):
lookup_field = 'movie'
queryset = MoviesGenres.objects.all()
serializer_class = MovieGenresSerializer

Resources