In rest api view I need to have 2 objects. For example:
class Foo(models.Model):
....
class Bar(models.Model):
....
What is correct way to get them? I mean how should I configure urls? I think this is not really good practice: url(r'^foo/(?P<pk>\d+)/bar/(?P<pk>\d+)/$', FooBarView.as_view())
Or: url(r'^foobar/$', FooBarView.as_view()) and then pass parameters: ?foo=1&bar=2.
I think it is more like a design problem.
Which one is better? I will say it depends on what is the relationship between model foo and model bar.
If you get model Class and model student, and you want to get student info and base on which class and relative student_number, like each class will have a student no.1.
you can go:
url(r'^class/(?P<class_pk>\d+)/student/(?P<student_pk>\d+)/$')
If you want to get both model information, but you want put them into a statistic table,
you can go:
url(r'^statistic/$')
then pass parameters like:
?class=1&student=2
These are just simple examples, there should be other cases, and you should use other way to design your URL API.
For django >= 1.5 You could pass parameters to class view doing somethin like this:
class Foo(TemplateView):
template_name = "yourtemplatesdir/template.html"
# Set any other attributes here
# dispatch is called when the class instance loads
def dispatch(self, request, *args, **kwargs):
self.id = kwargs.get('foo_id', "other_def_id")
self.barid = kwargs.get('bar_id', "other_def_id")
# depending on your definition, this class might be different
class Bar(TemplateView):
template_name = "yourtemplatesdir/template.html"
# Set any other attributes here
# this method might not be necessary
# dispatch is called when the class instance loads
def dispatch(self, request, *args, **kwargs):
self.id = kwargs.get('bar_id', "other_def_id")
In your url conf something like:
from .views import FooBar
urlpatterns = patterns('',
url(
regex=r'^(?P<foo_id>\d+)/(?P<bar_id>\d+)/$'
view=FooBar.as_view(),
name='viewname'
),
)
Then, in your template view for FooBar, you could do:
{{ view.id }}
{{ view.barid }}
I hope it helps, it could be more detailed if you provide further information about the object information needed in your 'action'.
Related
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.
Now my code is like:
def use_item(request):
itemname = request.get('value')
if itemname == 'item1':
#do something
if itemname == 'item2':
#do something else
Can I do it in the following way?
views.py
class use_item():
def use_item(self,request):
itemname = request.get('value')
use = getattr(self,itemname) # say itemname is 'item1'
use()
def item1(self,request):
#do something
def item2(self,request):
#do something else
I've tried the second method but it seems that I was not doing it right.
And the reason I want to do it in this way is that I hope to group the methods that they'd be more organized.
the actual code
#views.py
class use_item():
def useitem(self,request):
itemname = request.POST.get('value')
use = getattr(self,itemname)
use()
def jelly(self,request,topic_id):
t = topic.objects.get(id=topic_id)
t.top = True
t.time_topped = datetime.datetime.now()
t.save()
#urls.py
url(r'^use/item/(?P<topic_id>\d+)/$', 'use_item.use_item', name='use_item'),
If you want to have a better organization of your code, and reuse some code accross different views, instead of pasting it where you need, you may use the Django class based views:
# views.py
from django.views import View
class use_item(View):
def get(self, request, *args, **kwargs):
itemname = request.POST.get('value')
use = getattr(self,itemname)
use()
def item1(self,request):
#do something
def item2(self,request):
#do something else
# urls.py
from package.views import use_item
urlpatterns = [
# [...]
url(r'^use/item/(?P<topic_id>\d+)/$', use_item.as_view(), name='use_item'),
# [...]
]
But, if at some point you need to call item1() or item2() from another view (is it the reason you mentioned the other view jelly ?), you will see that it is not possible.
One solution could be moving the common methods in another class and make sure your views inherit it. This is often called mixins in django world.
# views.py
from django.views import View
class ItemsRelatedLMixin:
def item1(self, request):
#do something
def item2(self, request):
#do something else
class use_item(ItemsRelatedLMixin, View):
def get(self, request, *args, **kwargs):
itemname = request.POST.get('value')
use = getattr(self,itemname)
use()
class jelly(ItemsRelatedLMixin, View):
def get(self, request, topic_id):
t = topic.objects.get(id=topic_id)
t.top = True
t.time_topped = datetime.datetime.now()
t.save()
Now, your views jelly and use_item call the common methods. Of course you can define new methods in a view to make them available only from that view. You could also create class members to store values you use often etc. Keep in mind that each request received by Django will trigger creation of a new instance of your view class (you can't keep data stored between 2 requests in class members).
In Django, view functions are usually organized in modules and not in classes.
To keep things organized, use more than one views module: views.py, foo_views.py, bar_views.py, or: views/__init__.py, views/foo.py, views/bar.py.
You need to provide the view in the signature of the class. i.e.:
from django.views import [your_View_name]
Then provide the same view in class definition;
class use_item(your_View_name):
def useitem(self,request):
itemname = request.POST.get('value')
use = getattr(self,itemname)
use()
If you are defining your class for the same view,
class use_item(self):
def useitem(self,request):
itemname = request.POST.get('value')
use = getattr(self,itemname)
use()
You may refer Django docs on Class-Based-View for more in-depth knowledge.
UPDATE:
When you are calling your function useitem you need to use the instance of your class as follows:
user_instance = views.use_item() //Create instance of your class
user_instance.useritem() //call your function using above instance
I have a model which relates to many models, like this:
class Father:
son = # Foreign key to Son model
class Son:
#property
def son_daughters:
if ... :
obj = TypeA.objects.get(...)
elif ... :
obj = TypeB.objects.get(...)
else:
obj = TypeC.objects.get(...)
return obj
I would like to get Father data from daughter name or type. I have this filter class where I need to send two query set parameters related to daughter in order to get daughter ids and apply it as a filter to Father. This is my filter class:
class FatherFilter(django_filters.rest_framework.FilterSet):
def daughter(self, method_name, args, **kwargs):
print(method_name, args, kwargs)
...
daughter_id = django_filters.NumberFilter(method=daughter)
But when I call this endpoint I just get one query parameter and not all.
Is there a way to get the query parameters inside this method instead of just one?
Thanks in advance.
In order to achieve this, I found that Django Rest Framework has a class that extends from django_filters. This class is called BaseFilterBackend and can be used to extend the default backend for filtering any request. So what I did was adding a class extended from BaseFilterBackend like this:
from rest_framework import filters
class FatherFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
daughter_id = request.query_params.get("daughter_id", None)
daughter_name = request.query_params.get("daughter_name", None)
if daughter_id and daughter_name:
kwargs = {
daughter_name: daughter_id
}
queryset = queryset.filter(**kwargs)
return queryset
This filter will apply before others, so even if you are using a FilterSet class, you will not lose the filters from your BaseFilterBackend. The problem with this solution is that relies in rest_framework.filters package, a filter that its not related to django_filters.
This might not be the best way to achieve this, so if you have better ideas please add them to help others with a similar problem.
I'm trying to do cache_page with class based views (TemplateView) and i'm not able to. I followed instructions here:
Django--URL Caching Failing for Class Based Views
as well as here:
https://github.com/msgre/hazard/blob/master/hazard/urls.py
But I get this error:
cache_page has a single mandatory positional argument: timeout
I read the code for cache_page and it has the following:
if len(args) != 1 or callable(args[0]):
raise TypeError("cache_page has a single mandatory positional argument: timeout")
cache_timeout = args[0]
which means it wont allow more than 1 argument. Is there any other way to get cache_page to work?? I have been digging into this for sometime...
It seems like the previous solutions wont work any longer
According to the caching docs, the correct way to cache a CBV in the URLs is:
from django.views.decorators.cache import cache_page
url(r'^my_url/?$', cache_page(60*60)(MyView.as_view())),
Note that the answer you linked to is out of date. The old way of using the decorator has been removed (changeset).
You can simply decorate the class itself instead of overriding the dispatch method or using a mixin.
For example
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
#method_decorator(cache_page(60 * 5), name='dispatch')
class ListView(ListView):
...
Django docs on decorating a method within a class based view.
yet another good example CacheMixin
from cyberdelia github
class CacheMixin(object):
cache_timeout = 60
def get_cache_timeout(self):
return self.cache_timeout
def dispatch(self, *args, **kwargs):
return cache_page(self.get_cache_timeout())(super(CacheMixin, self).dispatch)(*args, **kwargs)
usecase:
from django.views.generic.detail import DetailView
class ArticleView(CacheMixin, DetailView):
cache_timeout = 90
template_name = "article_detail.html"
queryset = Article.objects.articles()
context_object_name = "article"
You can add it as a class decorator and even add multiple using a list:
#method_decorator([vary_on_cookie, cache_page(900)], name='dispatch')
class SomeClass(View):
...
I created this little mixin generator to do the caching in the views file, instead of in the URL conf:
def CachedView(cache_time=60 * 60):
"""
Mixing generator for caching class-based views.
Example usage:
class MyView(CachedView(60), TemplateView):
....
:param cache_time: time to cache the page, in seconds
:return: a mixin for caching a view for a particular number of seconds
"""
class CacheMixin(object):
#classmethod
def as_view(cls, **initkwargs):
return cache_page(cache_time)(
super(CacheMixin, cls).as_view(**initkwargs)
)
return CacheMixin
Yet another answer, we found this to be simplest and is specific to template views.
class CachedTemplateView(TemplateView):
#classonlymethod
def as_view(cls, **initkwargs): ##NoSelf
return cache_page(15 * 60)(super(CachedTemplateView, cls).as_view(**initkwargs))
Would like to add this:
If you need to use multiple decorators for cache like vary_on_headers and cache_page together, here is one way I did:
class CacheHeaderMixin(object):
cache_timeout = int(config('CACHE_TIMEOUT', default=300))
# cache_timeout = 60 * 5
def get_cache_timeout(self):
return self.cache_timeout
def dispatch(self, *args, **kwargs):
return vary_on_headers('Authorization')(cache_page(self.get_cache_timeout())(super(CacheHeaderMixin, self).dispatch))(*args, **kwargs)
This way cache is stored and it varies for different Authorization header (JWT). You may use like this for a class based view.
class UserListAPIView(CacheHeaderMixin, ListAPIView):
serializer_class = UserSerializer
def get_queryset(self):
return CustomUser.objects.all()
I didn't found a good cache solution for class based views and created my own: https://gist.github.com/svetlyak40wt/11126018
It is a mixin for a class. Add it before the main base class and implement method get_cache_params like that:
def get_cache_params(self, *args, **kwargs):
return ('some-prefix-{username}'.format(
username=self.request.user.username),
3600)
Here's my variation of the CachedView() mixin - I don't want to cache the view if the user is authenticated, because their view of pages will be unique to them (e.g. include their username, log-out link, etc).
class CacheMixin(object):
"""
Add this mixin to a view to cache it.
Disables caching for logged-in users.
"""
cache_timeout = 60 * 5 # seconds
def get_cache_timeout(self):
return self.cache_timeout
def dispatch(self, *args, **kwargs):
if hasattr(self.request, 'user') and self.request.user.is_authenticated:
# Logged-in, return the page without caching.
return super().dispatch(*args, **kwargs)
else:
# Unauthenticated user; use caching.
return cache_page(self.get_cache_timeout())(super().dispatch)(*args, **kwargs)
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')