Django class based views. Use same custom class for different url - django

I would like to know if it would be possible to create only a custom class for my views in django that could be valid for different urls.
For example:
#urls.py
url(r'^$', CustomClass.as_view(), name='index'),
url(r'^other_url/(?P<example>[-\w]+)$', CustomClass.as_view(), name='other')
#views.py
CustomClass(View):
# for / url
def first_method(self, request):
pass
# for other_url/
def second_method(self, request, example):
pass
I have readed the documentation about class based views, but in the example only talks about a single url...
https://docs.djangoproject.com/en/1.9/topics/class-based-views/intro/
So, I suppose I have to create a class for each url. But it would be possible use the same class with different methods for different url?

You don't need to create different classes for different urls. Although it is pretty redundant to have the same class in different urls, you could do:
url(r'^$', CustomClass.as_view(), name='index'),
url(r'^other_url/(?P<example>[-\w]+)$', CustomClass.as_view(), name='other')
exactly what you're doing. There are some cases where you have a generic class you want to use (either generic from the generics module/package, or generic in the OOP sense). Say, an example:
url(r'^$', CustomBaseClass.as_view(), name='index'),
url(r'^other_url/(?P<example>[-\w]+)$', CustomChildClass.as_view(), name='other')
Or even the same class with only different configuration (regarding generic classes (descending from View): accepted named parameters depend on how are they defined in your class):
url(r'^$', AGenericClass.as_view(my_model=AModel), name='index'),
url(r'^other_url/(?P<example>[-\w]+)$', AGenericClass.as_view(my_model=Other), name='other')
Summary You have no restriction at all when using generic views, or passing any kind of callable at all, when using url.

Related

Django rest_framework DefaultRouter() class vs normal url

I'm using REST in Django, And I couldn't understand what is the main difference between classic URL and instantiating DefaultRouter() for registering URL by ViewSet.
I have a model:
class Article(models.Model):
title = models.CharField()
body = models.TextField()
author = models.ForeignKey()
Serializing model like this:
from blog.models import Article
class ArticleSerializer(serializers.ModelSerializer):
class Meta:
model = Article
fields = ['title', 'body', 'author']
View Class:
from blog.models import Article
from rest_framework import viewsets
from .serializers import ArticleSerializer
class ArticleViewSet(viewsets.ModelViewSet):
serializer_class = ArticleSerializer
queryset = Article.objects.all()
and URLS:
router = DefaultRouter()
router.register(r'articles', ArticleViewSet)
urlpatterns = [
path('', include(router.urls)),
]
Is it possible to use classic URL in URLS.py instead of instantiating the object for a ViewSet like this:
urlpatterns = [
path('api/', 'views.someAPI'),
]
I just know HTTP method in ViewSet translate methods to retrieve, list and etc...
The Question is can we use traditional(Classic) URL style in this situation, Should we ?
Thanks for your help.
Well, in a nutshell as a django developer it is notorious how it is hard to deal with normal urls in django in some cases. Every now and again we get confused with the id type of the detail page that in some case are strings or integers with its regex, and so on.
For example:
urlpatterns = [
url(r'^(?P<content_type_name>[a-zA-z-_]+)$', views.content_type, name = 'content_type'),
]
# or
urlpatterns = [
url(r'^(?P<content_type_name>comics|articles|videos)$', views.content_type, name='content_type'),
]
Not mentioning that in almost every case its needed to have two urls like:
URL pattern: ^users/$ Name: 'user-list'
URL pattern: ^users/{pk}/$ Name: 'user-detail'
THE MAIN DIFFERENCE
However, using DRF routers the example above is done automatically:
# using routers -- myapp/urls.py
router.register(r"store", StoreViewSet, basename="store")
How django will understand it:
^store/$ [name='store-list']
^store\.(?P<format>[a-z0-9]+)/?$ [name='store-list']
^store/(?P<pk>[^/.]+)/$ [name='store-detail']
^store/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='store-detail']
See how much job and headache you have saved with a line of code only?
To contrast, according to DRF documentation the routers is a type of standard to make it easy to declare urls. A pattern brought from ruby-on-rails.
Here is what the documentation details:
Resource routing allows you to quickly declare all of the common
routes for a given resourceful controller. Instead of declaring
separate routes for your index... a resourceful route declares them in
a single line of code.
— Ruby on Rails Documentation
Django rest framework documentation:
Some Web frameworks such as Rails provide functionality for
automatically determining how the URLs for an application should be
mapped to the logic that deals with handling incoming requests.
REST framework adds support for automatic URL routing to Django, and
provides you with a simple, quick and consistent way of wiring your
view logic to a set of URLs.
For more details follow the django rest framework documentation.

Django - Retrieve url origin from view - multiple urls to 1 view

I have several urls mapping to one view. Inside the url, I would like to know the url origin? It is to avoid writing multiple views (DRY).
urlpatterns = [
path('path1/', views.IndexView.as_view(), name='index'),
path('path2/', views.IndexView.as_view(), name='index')
]
class IndexView(generic.ListView):
url_origin = ... #would like path1 or path2
I looked for 'reverse' but it does not seem to be a solution to my problem. It returns an error.
reverse('index')
Thank you in advance
You can obtain the original path with request.path [Django-doc] (so in a view function for a class-based view, you can access this with self.request.path.
You can however fix this, for example by providing an extra parameter, like:
urlpatterns = [
path('path1/', views.IndexView.as_view(), name='index1', kwargs={'through': 'path1'}),
path('path2/', views.IndexView.as_view(), name='index2', kwargs={'through': 'path2'})
]
Then you can inspect the self.kwargs['through'] parameter in your class-based view.
Note that you better give the different paths a different name, since otherwise, it will indeed raise an error. By giving these as name 'index1' and 'index2', you can simply use reverse('index1'), and it is not ambiguous to what URL it should redirect.

How is the Django view class executed?

I have a simple question to ask. How is the class executed defined the views.py? For example, if I have the path defined like the following, I assume that the snippet, 'views.PostListView.as_view()', is executing the PostListView defined in the views.py. Am I right?
urlpatterns = [
path('', views.PostListView.as_view(), name='post_list'),
path('about/', views.AboutView.as_view(), name='about'),
path('post/<int:pk>', views.PostDetailView.as_view(), name='post_detail'),
]
If you look at https://github.com/django/django/blob/master/django/views/generic/base.py (which is the base View class for generic views that all of your other views would generally inherit from), as_view is defined as a class/static method of the base view class, and it specifically returns a function view (def view(request, *args, **kwargs) ), which in turn takes a request object and then multipe optional args/kwargs. This view function is what gets passed to the urlpatterns. When an actual request comes in from the user, Django walks through the urlpatterns until it finds a match, and will then pass the request object and other information to the actual view function (so it gets executed once per matching request).
Hope that helps.

How to get rid of the version information in API url in tastypie django?

I am creating REST based APIs for an app using Tastypie with Django. The problem is default API url in Tastypie contains version info in url patterns i.e.
http://lx:3001/api/v1/vservers/?username=someuser&api_key=someapikey
I want my url to be free from API version info like this:
http://lx:3001/api/vservers/?username=someuser&api_key=someapikey
urls.py
v1_api = Api()
v1_api.api_name = ''
v1_api.register(UserResource())
...
url(r'^api/', include(v1_api.urls)),
I am overwriting api_name with an empty string still
http://lx:3001/api/vservers/?username=someuser&api_key=someapikey does not work.
How can I get rid of the version info altogether?
Thanks..
Subclass Api and override urls to remove all the api_name-related bits:
class MyApi(Api):
#property
def urls(self):
"""
Provides URLconf details for the ``Api`` and all registered
``Resources`` beneath it.
"""
pattern_list = [
url(r"^%s$" % trailing_slash(), self.wrap_view('top_level'), name="api_top_level"),
]
for name in sorted(self._registry.keys()):
pattern_list.append((r"^/", include(self._registry[name].urls)))
urlpatterns = self.override_urls() + patterns('',
*pattern_list
)
return urlpatterns
Although tastypie makes supplying api_name optional, failing to provide one simply defaults api_name to "v1". This behavior can be modified by subclassing Api and overriding the urls property within api.py to behave independently of api_name. To achieve the desired URLconf, however, there's still one correction to #dokkaebi's solution worth noting:
pattern_list.append((r"^/", include(self._registry[name].urls)))
should instead read:
pattern_list.append((r'', include(self._registry[name].urls)))
in order to avoid the dreaded // that would direct your clients to
http://lx:3001/api//vservers/?username=someuser&api_key=someapikey
in place of
http://lx:3001/api/vservers/?username=someuser&api_key=someapikey
as intended.
For convenience, I've included the modified code below.
Solution
class MyApi(Api):
"""
An API subclass that circumvents api_name versioning.
"""
#property
def urls(self):
"""
Provides URLconf details for the ``Api`` and all registered
``Resources`` beneath it.
"""
pattern_list = [
url(r"^%s$" % trailing_slash(), self.wrap_view('top_level'), name="api_top_level"),
]
for name in sorted(self._registry.keys()):
pattern_list.append((r'', include(self._registry[name].urls)))
urlpatterns = self.override_urls() + patterns('',
*pattern_list
)
return urlpatterns
Convention
One of Django’s core philosophies is that URLs should be beautiful; a clean, elegant URL scheme is an important detail in any high-quality Web application. With respect to the validity of this approach, using a custom request header or an accept header will get the versioning job done without convoluting the scheme with the (subjectively) ugly v1/. That is not to say that the URL versioning strategy is without its share of caveats; however, it's quick to implement and predictable in its response.

How to use url pattern named group with generic view?

I'm trying to display blog records for particular author using generic view:
urlpatterns = patterns('',
url(r'^blog/(?P<uid>[\d+])/$', ListView.as_view(
queryset=Blog.objects.filter(published=True, author=uid),
), name='blog_list'),
But I get NameError: name 'uid' is not defined
Is it possible to use urlconf named groups this way?
You need to create your own implementation of ListView like so:
class BlogListView(ListView):
model = Blog
def get_queryset(self):
return super(BlogListView, self).get_queryset().filter(
published=True, author__id=self.kwargs['uid'])
and then use it in your URLconf:
urlpatterns = patterns('',
url(r'^blog/(?P<uid>[\d+])/$', BlogListView.as_view(),
name='blog_list'),
The documentation for class-based generic views is, in my opinion, not quite up to scratch with the rest of the Django project yet - but there are some examples which show how to use ListView in this way:
https://docs.djangoproject.com/en/1.3/topics/class-based-views/#viewing-subsets-of-objects