I have a variable named email in view.
I want to access this in ManageSerializer.
How can I pass this argument in serializer and get there?
views.py
email = 'xyz#gmail.com'
interviewData = Manage.objects.
filter(catcher_id = userCheck['id'], acceptation = '1').
filter(invitation_date__gte = dateToday)[:5];
serializer = ManageSerializers(interviewData, many = True)
Maybe pass it as kwargs:
ManageSerializers(interviewData, many = True, email= email)
You can access this in the init of the Serializer, using something like:kwargs.pop('email')
OR
You can pass the context to the Serializer like this.
ManageSerializers(interviewData, many = True, context={'email': email})
and you can access the context in Serializer like self.context['email']
You should pass it in context variable:
serializer = ManageSerializers(interviewData, many=True, context={'email': email})
Docs: Including extra context
Related
I have a route that uses a ViewSet
router.register(
r'mapping_details',
GlobalMappingDetailsViewSet,
base_name='global-store-product-mappings'
)
This view set contains a get_queryset method and a serializer.
class GlobalMappingDetailsViewSet(viewsets.ModelViewSet):
serializer_class = GlobalMappingDetailsSerializer
def get_queryset(self):
return models.Mappings.objects.all().select_related('my_column')
class GlobalMappingDetailsSerializer(serializers.ModelSerializer):
class Meta:
model = models.Mappings
fields = ('column1', 'column2')
I want to add 2 fields that are populated using a single API call in the response of the request. I could use serializers.Field but will have to make separate calls for both the fields.
Does someone know the right way to handle this use case?
The way to do this is by overriding the Django Viewset actions. In my case I needed to just populate the response in the fields, I overrode the list and retrieve actions and added the fields to the response.
Some sample code:
def retrieve(self, request, *args, **kwargs):
response = super(GlobalMappingDetailsViewSet, self).retrieve(request, *args, **kwargs)
data = response.data
api_response = MyService.make_api_call(data.get('id'))
data['my_new_field'] = api_response.get('response_field', '')
return response
I have a view in django that sign's up a client and I have a model for the client and a form that looks like this:
from django.forms import ModelForm
from api.models.client import Client
class SignUpForm(ModelForm):
class Meta:
model = Client
fields = ['first_name', 'last_name']
In my view I would like to validate the data in the request but my problem is that the paramters in the request are camelCase and not snake_case so when I try to validate the data it doesn't work.
def sign_up(request):
body = json.loads(request.body)
form = SignUpForm(body)
print(form.is_valid())
return HttpResponse('this is a test response')
Is there a clean way of making this work? Also is this the correct way to do what I'm trying to do?
You can iterate through the body keys, use regex to rename the key and adding to a new dictionary.
def camel_to_snake(val):
return re.sub('([A-Z]+)', r'_\1', val).lower()
body = json.loads(request.body)
new_body = {camel_to_snake(k): v for k, v in body.items()}
What i currently have setted up with django rest is :
MODEL NAME : Animes
/api/ <- root
/api/animes/ <- lists all animes available in my db
/api/animes/id/ <- returns the anime instance that has id=id
MODEL NAME : Episodes
/api/ <- root
/api/episodes/ <- lists all episodes of all animes available in my db
/api/episodes/id/ <- returns the episode instance that has id=id
So basically im trying to achieve is :
if i request api/episodes/{anime_name}/
i get that specific anime's Episodes listed .
how can i do that ?
EpisodesSerializer
class EpisodesSerializer(serializers.ModelSerializer):
class Meta:
model = Episodes
fields = '__all__'
Router
router = routers.DefaultRouter()
router.register('animes', AnimesViewSet, 'animes')
router.register('episodes', EpisodesViewSet, 'episodes')
urlpatterns = router.urls
EpisodesViewSet
class EpisodesViewSet(viewsets.ModelViewSet):
permission_classes = [permissions.AllowAny]
MainModel = Episodes
queryset = Episodes.objects.all()
serializer_class = EpisodesSerializer
EDIT
As the OP mentioned in the comments, the newer versions of DRF use #action instead as #detail_route and #list_route are deprecated.
To use a different field for lookup, you can implement the logic to get the object yourself but you have to make sure that the field you're using for lookup is unique, else you can have many objects returned.
So assuming the anime name is unique and you want to use it for lookup, you can do this:
#action(detail=True, methods=['get'])
def episodes(self, *args, **kwargs):
anime = Anime.objects.get(name=self.kwarg[self.lookup_field])
episodes = Episode.objects.filter(anime=anime)
...
You can also check how get_object() is implemented to make it more robust.
I made a generic view mixin for myself that allows lookup with multiple unique fields aprt from the main pk lookup field:
class AlternateLookupFieldsMixin(object):
"""
Looks up objects for detail endpoints using alternate
lookup fields assigned in `alternate_lookup_fields` apart
from the default lookup_field. Only unique fields should be used
else Http404 is raised if multiple objects are found
"""
alternate_lookup_fields = []
def get_object(self):
try:
return super().get_object()
except Http404:
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
queryset = self.filter_queryset(self.get_queryset())
obj = None
for lookup_field in self.alternate_lookup_fields:
filter_kwargs = {lookup_field: self.kwargs[lookup_url_kwarg]}
try:
obj = get_object_or_404(queryset, **filter_kwargs)
except Http404:
pass
if obj:
self.check_object_permissions(self.request, obj)
return obj
raise Http404
All you have to do is add it to your view's base classes and add the fields for lookup(name in your case) in the alternate_lookup_fields attribute. Of course, only use unique fields.
As for filtering, you can check how simple filtering is done here.
However, I will recommend using a more generic filter backend like django-filter
ORIGINAL ANSWER
First of all, the url will look more initutive like this:
api/anime/<anime_id>/episodes/
This is because you should usually start from a more generic resource to more specific ones.
To achieve this, in your AnimeViewSet(not EpisodesViewSet), you can have a detail route for the episodes like this:
from rest_framework.decorators import detail_route
#detail_route(methods=['get'])
def episodes(self, *args, **kwargs):
anime = self.get_object()
episodes = Episode.objects.filter(anime=anime)
page = self.paginate_queryset(anime)
if page is not None:
serialier = EpisodesSerializer(page, context=self.get_serializer_context(), many=True)
return self.get_paginated_response(serializer.data)
serializer = EpisodesSerializer(episodes, context=self.get_serializer_context()) many=True)
return Response(serializer.data)
You could also just use a filter on the EpisodesViewSet to fetch episodes belonging to a particular anime this way:
api/episodes?anime=<anime_id>
In my models.py, I create a field that is supposed to be non-editable by the user:
request_status = models.CharField(
max_length = 100,
default = 'received',
editable = False
)
In the views.py file, I override the create() method (from the generics.ListCreateAPIView and CreateModelMixin superclasses) to activate a docker container and, if it is successfully done, I want to modify the request_status field to something like container_activated and then send back the HTTP response to the user.
So... is there a way of modifying a non-editable field? Is there a better way of setting up the model instead?
My best guess is that there is a way of modifying the data that gets registered in the API DB through one of the lines of the standard create() method (non-editable fields do not appear in the data property):
serializer = self.get_serializer(data = request.data)
serializer.is_valid(raise_exception = True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
editable=False doesn't mean you can't edit the field at all, it only means it won't show up in the admin or modelforms by default. In DRF, it means it will be automatically set as read_only; but you can override this simply defining the field manually.
class MySerializer(serializers.ModelSerializer):
request_status = serializers.CharField(max_length=100, default='received')
class Meta:
...
This is kind of a circumvention but it is working more or less well. I changed my field back to have editable = True and then I simply forced it to be a default locked (hardcoded) value. In the end, inside the create() of the views.py class, you put something like:
def create(
self,
request,
*args,
**kwargs
):
# Settings of the POST request
req_data = request.data.dict()
req_data['request_status'] = 'not yet processed'
# Is everything OK?
if processing_OK == 0:
req_data['request_status'] = 'processed'
# Save the data
serializer = self.get_serializer(data = req_data)
serializer.is_valid(raise_exception = True)
serializer.save() # `self.perform_create(serializer)` originally
headers = self.get_success_headers(serializer.data)
# Return Response
return Response(
data = serializer.data,
status = status.HTTP_201_CREATED,
headers = headers
)
The key part was understanding that you can override what goes into the HTTP POST inside the API by altering the dictionary from request.data.dict(). Then you simply pass that dict (usually it's a QueryDict actually) inside the self.get_serializer() and follow the standard create() method.
If you configure editable = False, you're not able to force the changes in the way I posted here. There's probably a better way of doing this in the end.
Very begginer with the Django class based view.
I had a ListView that worked well but displayed all the objects. I wanted to filter this, and here what I did, following some examples found:
models.py:
class FolderElement(TimeStampedModel):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
class FolderMedia(TimeStampedModel):
folder_element = models.ForeignKey(FolderElement)
file = models.FileField(upload_to=generate_filepath_folder)
slug = models.SlugField(max_length=50, blank=True)
views.py:
class FolderMediaListView(ListView):
model = FolderMedia
template_name = "book.html"
def get_queryset(self):
self.folder_element = get_object_or_404(FolderElement,
pk=self.kwargs['pk'])
return FolderMedia.filter(folder_element=self.folder_element)
def render_to_response(self, context, **response_kwargs):
files = [ serialize(p) for p in self.get_queryset() ]
data = {'files': files}
response = JSONResponse(data, mimetype=response_mimetype(self.request))
response['Content-Disposition'] = 'inline; filename=files.json'
return response
But now that I overrided the get_queryset() method, I don't understand how I'm supposed to inject the pkparameter to the view sothat the filter works. Currently, using pdb, I can see that self.kwargs equals {} into the get_queryset() method.
Thanks.
The keyword arguments (kwargs) that the Django URL dispatcher passes to the view comes from the following:
Captured parameters in the URL expression
Additional arguments specified in the URL definition
All of them in urls.py.
So, for example, in order to get an ID form the URL in a form: /folder/id/:
url(r'folder/(?P<pk>\d+)/', FolderMediaListView.as_view)
Or if the id is constant (more rarely), you can pass it as an additional argument:
url(r'folder/', FolderMediaListView.as_view, {'pk': 1})
More information on the subject in the Django documentation.
You need to supply it in the URL. For example:
url(r'folder/(?P<id>\d+)/media', FolderMediaListView.as_view, name='folder_media_list')