Calling class view inside of another class view in Django - python

Seems like duplicate of this one, but still: I have JobCreateViewV1 (old one) view class which works with model in one way, and after upgrade, i created JobCreateViewV2 (new one) class that adds another entry to another model entry of first call.
What is the best way to get response form first class and pass it to the second one?
Here are my classes:
JobCreateView
class JobCreateView(AuthenticatedView, generics.GenericAPIView):
#some seralizer
serializer_class = JobSerializerForUser
def post(self, request):
#....yada yada yada...
job='model is here'
return Response(status=status.HTTP_201_CREATED, data=self.serializer_class(job, many=False).data)
and JobCreateViewV2
class JobCreateViewV2(AuthenticatedView, generics.ListAPIView):
serializer_class = ResponsibleTechnicianSerializer
def post(self, request):
JobCreateViewV1Response = JobCreateViewV1.post(self, request)
# ...yada yadayada...
#and here JobCreateViewV1Response equals to {}
Old url works perfect, but when i call V2 one, data portion of response from V1 calls equals to {}. Might this problem occur because i call JobCreateView in improper way and serializer is not working the way it should?
I've tried to use ShowAppsView.as_view()(self.request) approach, but it seems is not what i need.

OldViewClass.post(self, request) works, but my problem was the old class inherits new class serializer, so old serializer should be explicitly passed to the old class.

Related

DRF - Create a Viewset Mixin/Baseclass to inherit a viewset-action

I have a website that is essentially a wiki for a DnD campaign that I am participating in. As such it has articles of Creatures, Characters, Locations and more. I wanted to use Viewsets to access them easily and wanted to use a Viewset action (together with a custom router) to be able to look for individual records not through pk, but through various query-parameters.
I already have something that works for this, now I would like to apply some inheritance to it to not repeat myself. What I'd like to do is something like this:
class WikiBaseViewset (viewsets.ModelViewSet):
detail_with_params_url_pattern_suffix: str
#action(detail=True, url_name="detail-params", url_path=detail_with_params_url_pattern_suffix)
def detail_through_params(self, request, **kwargs):
if self.detail_with_params_url_pattern_suffix == "":
raise InvalidViewsetURLException("URL of view 'detail_through_params' of WikiBaseViewset is not defined!")
model = self.serializer_class.Meta.model
instance = get_object_or_404(model, **kwargs)
serializer = self.get_serializer(instance)
return Response(serializer.data)
class CharacterSerializer (serializers.HyperlinkedModelSerializer):
class Meta:
model = wiki_models.Character
fields = '__all__'
class CharacterViewSet(WikiBaseViewset):
"""Called with URLs: character, character/<str: name>"""
serializer_class = CharacterSerializer
queryset = wiki_models.Character.objects.all()
detail_with_params_url_pattern_suffix = "(?P<name__iexact>.+)"
However, I'm struggling over the fact that the decorator absolutely requires the URL parameter in the base class. Otherwise the code just doesn't compile due to a NameError complaining that detail_with_params_url_pattern_suffix is not defined. If you were to set detail_with_params_url_pattern_suffix="" in the base-class in order to not get an Error when your code is compiled, that still wouldn't matter, as the decorator from my experiments so far still grabs the value of that variable from WikiBaseViewset not CharacterViewSet.
How can I rewrite my BaseClass so that this works? Is there even a way?
I did not find a fully satisfying answer to this problem, but in the end acquiesced with this solution, as it was better than copy pasting.
You might not be able to inherit viewset actions, but you sure can inherit individual methods and then just overwrite them in the child and throw a decorator on top. This lead to this structure:
class WikiBaseViewset (viewsets.ModelViewSet):
detail_with_params_url_pattern_suffix: str
def detail_through_params(self, request, **kwargs):
model = self.serializer_class.Meta.model
instance = get_object_or_404(model, **kwargs)
serializer = self.get_serializer(instance)
return Response(serializer.data)
class CharacterSerializer (serializers.HyperlinkedModelSerializer):
class Meta:
model = wiki_models.Character
fields = '__all__'
class CharacterViewSet(WikiBaseViewset):
"""Called with URLs: character, character/<str: name>"""
serializer_class = CharacterSerializer
queryset = wiki_models.Character.objects.all()
#action(detail=True, url_name="detail-params", url_path="(?P<name__iexact>.+)")
def detail_through_params(self, request, **kwargs):
return super().detail_through_params(request, **kwargs)

Where should I call custom method in django CBV? (DetailView)

I made a custom method that save session when call get() method in DetailView page.
Here's sample of my code.
class CustomDetailView(DetailView):
model = Jobs
template_name = "jobs/job_detail.html"
context_object_name = "job"
def custom_save_session(self, request):
# save session and so on
...
I wonder where should I call custom_save_session method.
I tried to call it inside get(), get_context_data() and so on.
All work well, but I have curiosity what is the best method.
Should I call inside get() method? Cause I want to save session data every time get() called.
Is there any method that call custom method when every time get() called in DetailView?
(I also wonder not only DetailView, but also all CBV in django too)
We can call extra methods in class based DetailView in two ways.
By overriding disatch method
By overriding get method
dispach method
class CustomDetailView(DetailView):
model = Jobs
template_name = "jobs/job_detail.html"
context_object_name = "job"
def custom_save_session(self, request):
pass
def dispatch(self, request, *args, **kwargs):
self.custom_save_session(request)
return super(CustomDetailView, self).dispatch(request, *args, **kwargs)
get method
class CustomDetailView(DetailView):
model = Jobs
template_name = "jobs/job_detail.html"
context_object_name = "job"
def custom_save_session(self, request):
pass
def get(self, request, *args, **kwargs):
self.custom_save_session(request)
return super(CustomDetailView, self).get(request, *args, **kwargs)
Both of the above ways is fine. But, I will go with dispatch method because we are dealing with session data so, it would be better.
Every class based view has an order of running things, each with it's own method.
CBV have a dedicated method for each step of execution.
You would call your custom method from the method that runs the step where you want to call your custom method from.
So you have to decide where your custom method should run, and define your own method on top of the view generic method for this step.
More from docs
In the url the CBV returns as_view(), which is callable function that gets a reuqest. From this entry point (actually the CBV dispatch method), the CBV runs all the steps.

In a Django serializer, take default field value from context (or request data)

I'm using Django with the REST Framework. In a serializer, I would like to assign a field value based on a view or request (request.data['type']) parameter, so I need the view/request in the context.
I succeeded, but only in a cumbersome way, and I am looking into ways to simplify the code. Here's the successful approach (omitting irrelevant fields):
class TypeDefault(object):
def set_context(self, serializer_field):
view = serializer_field.context['view'] # or context['request']
self.type = view.kwargs['type'].upper()
def __call__(self):
return self.type
class RRsetSerializer(serializers.ModelSerializer):
type = serializers.CharField(read_only=True, default=serializers.CreateOnlyDefault(TypeDefault()))
class Meta:
model = RRset
fields = ('type',)
read_only_fields = ('type',)
To simplify things, I tried removing the TypeDefault class, and replacing the type serializer field by
type = serializers.SerializerMethodField()
def get_type(self, obj):
return self.context.get('view').kwargs['type'].upper() # also tried self._context
However, context.get('view') returns None. I am unsure why the view context is not available here. My impression is that it should be possible to get the desired functionality without resorting to an extra class.
As a bonus, it would be nice to specify the default in the field declaration itself, like
type = serializers.CharField(default=self.context.get('view').kwargs['type'].upper())
However, self is not defined here, and I'm not sure what the right approach would be.
Also, I am interested if there is any difference in retrieving information from the view or from the request data. While the context approach should work for both, maybe there's a simpler way to get the CreateOnlyDefault functionality when the value is obtained from request data, as the serializers deals with the request data anyways.
Edit: Per Geotob's request, here is the code of the view that calls the serializer:
class RRsetsDetail(generics.ListCreateAPIView):
serializer_class = RRsetSerializer
# permission_classes = ... # some permission constraints
def get_queryset(self):
name = self.kwargs['name']
type = self.kwargs.get('type')
# Note in the following that the RRset model has a `domain` foreign-key field which is referenced here. It is irrelevant for the current problem though.
if type is not None:
return RRset.objects.filter(domain__name=name, domain__owner=self.request.user.pk, type=type)
else:
return RRset.objects.filter(domain__name=name, domain__owner=self.request.user.pk)
In urls.py, I have (among others):
url(r'^domains/(?P<name>[a-zA-Z\.\-_0-9]+)/rrsets/$', RRsetsDetail.as_view(), name='rrsets'),
url(r'^domains/(?P<name>[a-zA-Z\.\-_0-9]+)/rrsets/(?P<type>[A-Z]+)/$', RRsetsDetail.as_view(), name='rrsets-type'),
SerializerMethodField is a read-only field so I do not think it will work unless you set a default value... and you are back to the same problem as with CharField.
To simply things you could get rid of serializers.CreateOnlyDefault:
class RRsetSerializer(serializers.ModelSerializer):
type = serializers.CharField(read_only=True, default=TypeDefault())
If you want something more dynamic, I can only think of something like this:
class FromContext(object):
def __init__(self, value_fn):
self.value_fn = value_fn
def set_context(self, serializer_field):
self.value = self.value_fn(serializer_field.context)
def __call__(self):
return self.value
class RRsetSerializer(serializers.ModelSerializer):
type = serializers.CharField(read_only=True,
default=FromContext(lambda context: context.get('view').kwargs['type'].upper()))
FromContext takes a function during instantiation that will be used to retrieve the value you want from context.
All in all, your second approach above is the correct one:
Use serializers.SerializerMethodField and access self.context from the serializer method:
class SomeSerializer(serializers.ModelSerializer):
type = serializers.SerializerMethodField()
def get_type(self, obj):
return self.context['view'].kwargs['type'].upper()
The view, request and format keys are automatically added to your serializer context by all of the DRF generic views (http://www.django-rest-framework.org/api-guide/generic-views/#methods at the end of the section). This works just fine.
If you are creating a serializer instance manually, you will have to pass context=contextDict as an argument, where contextDict is whatever you need it to be (http://www.django-rest-framework.org/api-guide/serializers/#including-extra-context).
As #Michael has pointed out in another answer, the SerializerMethodField will be read only. But going by your first example (type = serializers.CharField(read_only=True.....) this seems to be what you want.

Use decorated methods (#property) in datatable's fields array (django-eztables)

I'm using django-eztables app (0.3.2), and I'm trying to use some model's properties within fields:
model's class:
class AModel(models.Model):
...
#property
def a_property(self):
return 'something'
eztables' view:
class AView(DatatablesView):
model = AModel
fields = [
...,
"a_property",
]
According to what I saw, this cannot work because method process_dt_response in eztables' DatatablesView class uses .values(*self.get_db_fields()) on the queryset.
The only way to do this is that I found is to overwrite mentioned method within my AView class, and just remove mentioned values call:
def process_dt_response(self, data):
self.form = DatatablesForm(data)
print self.form
if self.form.is_valid():
self.object_list = self.get_queryset()
return self.render_to_response(self.form)
else:
return HttpResponseBadRequest()
Is this ok (will it have some other bad side-effects)? Is there any other, better way to do this?
One strategy that worked for me was to append additional data just before it was returned to the client. render_to_response() calls get_row() to get the final array (for each record). Override get_row() and append custom data. One assumption here though is that all the data required to get/generate the additional custom data is available in get_row().

can you use Form Wizard for Model Forms in django?

I have one model and I've created a form out of the model using ModelForm. Now, I want to spread the form across two pages. For example, the first three fields will appear on the first page then the user clicks next and the last three fields appear on the second page. Then he clicks submit and the user submitted data is added to the database.
I took a look at the docs for the Form Wizard and it seems like it would work for model forms as well? Can someone confirm this?
And if it does, can someone explain the process of creating a WizardView class.
This example is given in the docs and I don't understand what the second two parameters are. Is form_list just a list of form objects that you've instantiated based on your form classes? And what is **kwargs?
class ContactWizard(SessionWizardView):
def done(self, form_list, **kwargs):
do_something_with_the_form_data(form_list)
return HttpResponseRedirect('/page-to-redirect-to-when-done/')
Thanks in advance for your help!
Say your model has two fields
class AModel( Model ):
fieldA = CharField()
fieldB = CharField()
We want to set each field in a separate step using a FormWizard. So we create two ModelForms, each showing one field:
class Form1( ModelForm ):
class Meta:
model = AModel
fields = ( 'fieldA', )
class Form2( ModelForm ):
class Meta:
model = AModel
fields = ( 'fieldB', )
We call our form wizard AWizard; the url.py entry should look something like
url( r'^$', AWizard.as_view( [ Form1, Form2 ] ) ),
In the implementation of AWizard we need to make sure all the forms write their data to a single instance, which we then save to the database:
class AWizard( SessionWizardView ):
instance = None
def get_form_instance( self, step ):
if self.instance is None:
self.instance = AModel()
return self.instance
def done( self, form_list, **kwargs ):
self.instance.save()
Notice that we override the method get_form_instance. This method returns the model instance the forms bind to.
You might think (I did), that this method creates an instance for the first request (the first step of the wizard), and then keeps using that same instance for all steps.
Actually, it's a little more complicated. For each request a new instance of AWizard is created, which in turn creates a new AModel instance. So, the steps don't share a single instance to start with.
The magic happens when the last form is submitted. At this point all forms are revalidated, each form calls get_form_instance and they end up populating a single AModel instance.
That instance is then saved in done.
Form Wizard is being built into Django 1.4 so is a good way to go about this. It should do what you want, but you may need a couple of tweaks.
Don't worry about the kwargs in done() at the moment - you're not going to need them.
form_list is the list of forms that you want to use for your steps - from urls.py
urlpatterns = patterns('',
(r'^contact/$', ContactWizard.as_view([ContactForm1, ContactForm2])),
)
[ContactForm1, ContactForm2] will be passed to done() as form_list.
What you will need to do is break your ModelForm into separate forms. The easiest way to do this (if you want your model on several forms) is to not use ModelForm but just create your own form. It's pretty easy:
from django import forms
class ContactForm1(forms.Form):
subject = forms.CharField(max_length=100)
sender = forms.EmailField()
class ContactForm2(forms.Form):
message = forms.CharField(widget=forms.Textarea)
Once your forms reflect the portions of your model, just create the views and patterns as described in the docs and set do_something_with_the_form_data(form_list) to a function that completes your model from the form data and then does a save.
You could use ModelForm but - only if you can persuade it to produce different forms for Form Wizard to use for each step - that's going to be the tricky part.
The view proposed by #wuerg did not work for me, I had to do this:
class AWizard( SessionWizardView ):
def dispatch(self, request, *args, **kwargs):
self.instance = AModel()
return super(ApplyWizard, self).dispatch(request, *args, **kwargs)
def get_form_instance( self, step ):
return self.instance
def done( self, form_list, **kwargs ):
self.instance.save()
return HttpResponseRedirect(reverse(thanks))
I had to alter the solution of #wuerg and #madmen to work in my usecase (saving the Model after every step). The big advantage of this approach is that it always uses the same instance of the AModel instead of creating a new instance for every step:
class AWizard(SessionWizardView):
instance = AModel()
def dispatch(self, request, *args, **kwargs):
return super(AWizard, self).dispatch(request, *args, **kwargs)
def get_form_instance(self, step):
return self.instance
def done(self, form_list, **kwargs):
self.save_model()
return render_to_response('done.html')

Categories

Resources