I use generic views/class-based views in my project and I want to call some functions if view is done successfully. I use for this def get_success_url() method. But I can't reach the model in that function. How can I get rid of this, or are there any other way to do this?
My codes:
class MyModelUpdate(UpdateView):
model = MyModel
fields = ['details']
def get_success_url(self, **kwargs):
add_log(form.model, 2, 1, request.POST.user)
return reverse_lazy('model-detail', kwargs = {'pk' : self.kwargs['model_id'] })
The UpdateView class implements UpdateMixin, which has methods such as form_valid, which is only called if the form data is valid.
So you could do:
class MyModelUpdate(UpdateView):
model = MyModel
fields = ['details']
def get_success_url(self, **kwargs):
return reverse_lazy('model-detail', kwargs={'pk': self.kwargs['model_id']})
def form_valid(self, form):
# The super call to form_valid creates a model instance
# under self.object.
response = super(MyModelUpdate, self).form_valid(form)
# Do custom stuff here...
add_log(self.object, 2, 1, self.request.POST.user)
return response
Related
I want to use my own method, which will return JsonResponse, but this method isn't called in this View by default. So maybe i need to rewrite another method or?
views.py:
class CreatePostJS(LoginRequiredMixin, SelectRelatedMixin, generic.CreateView):
fields = ('post_message', 'post_image')
model = models.Post
select_related = ('user',)
template_name = 'posts/post_list.html'
template_name = 'posts/post_form.html'
response_data = {}
response_data['text'] = model.post_message
response_data['posted'] = model.posted_at
response_data['user'] = model.user
def create_post(request):
if request.method == 'POST':
return JsonResponse(serializers.serialize('json', response_data), safe = False)
def form_valid(self, form):
self.object = form.save(commit = False)
self.object.user = self.request.user
self.object.save()
return super().form_valid(form)
https://docs.djangoproject.com/en/3.1/topics/class-based-views/intro/
Basically if you want to execute the method on POST requests, you can override the post method with your own. It will look more or less like this:
def post(self, request, *args, **kwargs):
my_json_response = self.create_post(request)
return super().post(request, *args, **kwargs)
That way there is no need to check for request.method in create_post, unless you plan to use it elsewhere.
When you work with generic views and want to override the default request handler, you need to overwrite the default method handler for the request method. In your case, it's the post() method from CreateView class.
I strongly recommend OOP way, so it would look like:
def get_request_data(self):
return {
'text': self.model.post_message,
'posted': self.model.posted_at,
'user': self.model.user
}
def create_post(request):
return JsonResponse(serializers.serialize('json', self.get_response_data()), safe = False)
def post(self, request, *args, **kwargs):
return self.create_post(request)
Note the if request.method == 'POST': is not needed, as post() method will only execute when POST request is being made in the current view.
You tried to initialize response_data outside a method, so as in my code you'd need to create another method for this - which I included in my example code.
I have test like that:
def test_getting_delete_view_invokes_point_changes_and_returns_status_200(self, point_changes):
request = RequestFactory().get(reverse('questions:delete-help', kwargs={'pk': 1}))
view = DeleteHelp.as_view()
view.kwargs['pk'] = 1
response = view(request)
And my view function:
class DeleteHelp(DeleteView, LoginRequiredMixin):
model = Help
template_name = 'confirm_deletion.html'
def get_object(self, queryset=None):
return get_object_or_404(Help, pk=self.kwargs['pk'], author=self.request.user)
def get_success_url(self):
point_pk = self.object.answer_to.plan_point.point_of.id
point_changes(point_obj=self.object.answer_to.plan_point)
return reverse('plans:display-public', args=[point_pk])
The question is, how am I supposed to pass 'pk' there? I keep getting an error KeywordError 'pk' in get_object method. If I use self.client to access this view then it works (why?), but I want to use RequestFactory.
Any help would be appreciated.
You pass it when you call the view.
view = DeleteHelp.as_view()
response = view(request, pk=1)
I have a mixin on top of my app/views.py:
class RefereeViewMixin(object):
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
if not self.request.user.is_superuser and \
not self.request.user.has_group("referee"):
raise PermissionDenied
return super(RefereeViewMixin, self).dispatch(*args, **kwargs)
And also a view which is derived from my custom mixin and CreateView:
class RefereeResultCreateView(RefereeViewMixin, CreateView):
category = None
template_name = "referee/result_create.html"
fields = ["minutes", "seconds", "miliseconds", "disqualification"]
def get_success_url(self):
return reverse("order_list", args=[self.category])
def get_context_data(self, **kwargs):
context = super(RefereeResultCreateView, self).get_context_data(
**kwargs)
context["project"] = Project.objects.get(pk=self.kwargs.get("pk"))
return context
def form_valid(self, form):
result = form.save(commit=False)
result.project = Project.objects.get(pk=self.kwargs.get("pk"))
result.save()
messages.success(self.request, _(
"Result entry for {} project created.".format(result.name)))
return super(RefereeResultCreateView, self).form_valid(form)
Finally, my final view which is derived from RefereeResultCreateView:
class RefereeFireFighterResultCreateView(RefereeResultCreateView):
model = FireFighterResult
category = "fire_fighter"
fields = RefereeResultCreateView.fields + [
"extinguish_success", "extinguish_failure", "wall_hit"]
And the pattern is like that:
url(r'^fire_fighter/(?P<pk>\d+)/create/$',
RefereeFireFighterResultCreateView.as_view(),
name="referee_fire_fighter_result_create")
However, when I go to /app/fire_fighter/some_id/create/ I got TypeError. It tells me super(type, obj): obj must be an instance or subtype of type. When I debug it, I see that RefereeResultCreateView's get_context_data method causes that error. If I don't override that method, I don't see any errors. When I override it at RefereeFireFighterResultCreateView class, I don't see any errors.
I can overcome the problem. However, I think it is not the elegant way. Actually, it is very dirty. Also, I am curious about the reason. Why it doesn't work in the way I expect? What is the elegant solution?
UPDATED
Here's the traceback:
https://gist.github.com/ilkerkesen/e65fefa66890758bcdcd
I want to pass some arguments to DRF Serializer class from Viewset, so for I have tried this:
class OneZeroSerializer(rest_serializer.ModelSerializer):
def __init__(self, *args, **kwargs):
print args # show values that passed
location = rest_serializer.SerializerMethodField('get_alternate_name')
def get_alternate_name(self, obj):
return ''
class Meta:
model = OneZero
fields = ('id', 'location')
Views
class OneZeroViewSet(viewsets.ModelViewSet):
serializer_class = OneZeroSerializer(realpart=1)
#serializer_class = OneZeroSerializer
queryset = OneZero.objects.all()
Basically I want to pass some value based on querystring from views to Serializer class and then these will be allocate to fields.
These fields are not include in Model in fact dynamically created fields.
Same case in this question stackoverflow, but I cannot understand the answer.
Can anyone help me in this case or suggest me better options.
It's very easy with "context" arg for "ModelSerializer" constructor.
For example:
in view:
my_objects = MyModelSerializer(
input_collection,
many=True,
context={'user_id': request.user.id}
).data
in serializers:
class MyModelSerializer(serializers.ModelSerializer):
...
is_my_object = serializers.SerializerMethodField('_is_my_find')
...
def _is_my_find(self, obj):
user_id = self.context.get("user_id")
if user_id:
return user_id in obj.my_objects.values_list("user_id", flat=True)
return False
...
so you can use "self.context" for getting extra params.
Reference
You could in the YourView override get_serializer_context method like that:
class YourView(GenericAPIView):
def get_serializer_context(self):
context = super().get_serializer_context()
context["customer_id"] = self.kwargs['customer_id']
context["query_params"] = self.request.query_params
return context
or like that:
class YourView(GenericAPIView):
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.context["customer_id"] = request.user.id
serializer.context["query_params"] = request.query_params
serializer.is_valid(raise_exception=True)
...
and anywhere in your serializer you can get it. For example in a custom method:
class YourSerializer(ModelSerializer):
def get_alternate_name(self, obj):
customer_id = self.context["customer_id"]
query_params = self.context["query_params"]
...
To fulfill the answer of redcyb - consider using in your view the get_serializer_context method from GenericAPIView, like this:
def get_serializer_context(self):
return {'user': self.request.user.email}
A old code I wrote, that might be helpful- done to filter nested serializer:
class MySerializer(serializers.ModelSerializer):
field3 = serializers.SerializerMethodField('get_filtered_data')
def get_filtered_data(self, obj):
param_value = self.context['request'].QUERY_PARAMS.get('Param_name', None)
if param_value is not None:
try:
data = Other_model.objects.get(pk_field=obj, filter_field=param_value)
except:
return None
serializer = OtherSerializer(data)
return serializer.data
else:
print "Error stuff"
class Meta:
model = Model_name
fields = ('filed1', 'field2', 'field3')
How to override get_serializer_class:
class ViewName(generics.ListAPIView):
def get_serializer_class(self):
param_value = self.context['request'].QUERY_PARAMS.get('Param_name', None)
if param_value is not None:
return Serializer1
else:
return Serializer2
def get_queryset(self):
.....
Hope this helps people looking for this.
List of element if your query is a list of elements:
my_data = DataSerializers(queryset_to_investigate,
many=True, context={'value_to_pass': value_passed}
in case off single data query:
my_data = DataSerializers(queryset_to_investigate,
context={'value_to_pass': value_passed}
Then in the serializers:
class MySerializer(serializers.ModelSerializer):
class Meta:
fields = '__all__'
model = 'Name_of_your_model'
def on_representation(self, value):
serialized_data = super(MySerializer, self).to_representation(value)
value_as_passed = self.context['value_to_pass']
# ..... do all you need ......
return serialized_data
As you can see printing the self inside on_representation you can see: query_set: <object (x)>, context={'value_to_pass': value_passed}
This is a simpler way, and you can do this in any function of serializers having self in the parameter list.
These answers are far to complicated; If you have any sort of authentication then add this property to your serializer and call it to access the user sending the request.
class BaseSerializer(serializers.ModelSerializer):
#property
def sent_from_user(self):
return self.context['request'].user
Getting the context kwargs passed to a serializer like;
...
self.fields['category'] = HouseCategorySerializer(read_only=True, context={"all_fields": False})
...
In your serializer, that is HouseCategorySerializer do this in one of your functions
def get_houses(self, instance):
print(self._context.get('all_fields'))
Using self._context.get('keyword') solved my mess quickly, instead of using self.get_extra_context()
I have the following hierarchy of classes:
class ProfileUpdateView( UpdateView, LoggerMixin ):
def get_context_data(self, **kwargs):
context = super(ProfileCreateView, self).get_context_data(**kwargs)
...
return context
UpdateView is in fact django.views.generic.UpdateView
class EventViewMixin(object):
template_name = ...
model = Event
form_class = ...
def get_success_url(self):
return self.success_url + str(self.object.id)
Class UpdateEventView mixes ProfileUpdateView and EventViewMixin
class UpdateEventView(ProfileUpdateView, EventViewMixin):
def form_valid(self, form):
...
return super(UpdateEventView, self).form_valid(form)
The problem in that for some reason the field "model=Event" is not visible to the framework when
it tries to use UpdateEventView. So I get the error:
UpdateEventView is missing a queryset. Define UpdateEventView.model, UpdateEventView.queryset, or override UpdateEventView.get_object().
What am I missing?
DISCLAIMER: I'm sort of a newbie to Python/Django.
So my question in sort of dumb ...
The problem is in the order of the mixins:
class UpdateEventView(ProfileUpdateView, EventViewMixin):
....
must be replaced with:
class UpdateEventView( EventViewMixin, ProfileUpdateView ):
This is because ProfileUpdateView has in its inheritance tree a field "model=None",
and if ProfileUpdateView is on the first position in the that is the value that
will be considered. If EventViewMixin comes first, then the correct value is taken.