I am using django with django-braces. This is to ask for an opinion if this is the right approach.
For this example I am just trying to return Users and a particular user in json format using CBV.
Views
class ListAllUsers(JSONResponseMixin, DetailView):
model = User
json_dumps_kwargs = {u"indent": 2}
def get(self, request, *args, **kwargs):
object = self.get_object()
context_dict = collections.defaultdict(list)
if not self.kwargs.get("pk"):
for obj in object:
context_dict[obj.id].append ({
u"name": obj.username,
u"email": obj.email
})
else:
context_dict[object.id].append ({
u"name": object.username,
u"email": object.email
})
return self.render_json_response(context_dict)
def get_object(self):
if not self.kwargs.get("pk"):
return User.objects.all()
else:
return get_object_or_404(User, pk=self.kwargs.get("pk"))
urls
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^users/$', ListAllUsers.as_view(), name="users-list"),
url(r'^users/(?P<pk>[0-9]+)/$', ListAllUsers.as_view(), name="users-detail")
]
I know it's a subjective question but I want your opinions please be supportive because I am having a hard time figuring out how to use CBVs optimally.
You're actually using a function-based view within a DetailView class based view (the if statement is the function determining whether to return one object or all the objects). DetailView is a Django class based view for showing detail on one object. You should make this view do that one thing only.
To show a list of objects, you should use ListView.
One solution is to write a second ListView and call it from your urls.py rather than calling your DetailView for both of your url endpoints.
Related
Well, actually the question is in the title :)
I want to have an UpdateView with different redirects depending on the referer from which the update is called.
Update: Initially, my question was misleading. I did not only want to access the meta data, but especially the http_referer of the site which led to the updateview.
It really depends on a view. If you use generic CBV you should call self.request.meta more info here. But if you just extend the View class you can do it like in the docs namely
from django.http import HttpResponse
from django.views import View
class MyView(View):
def get(self, request):
# <view logic>
request.meta
return HttpResponse('result')
I solved it by using
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['ref'] = self.request.META['HTTP_REFERER']
return context
def get_success_url(self):
if 'uebersichteingaben_nichtabgeschlossen' in self.request.POST['ref']:
return reverse_lazy(url_app_name + 'uebersichteingaben_nichtabgeschlossen')
else:
return reverse_lazy(url_app_name + 'uebersichteingaben_alle')
in the CBV. And I used the context['ref'] as hidden input in the template.
I am trying to write a view in which a post can be created and in the same page, the object_list will be displayed. And even an object can be updated and deleted.
Country Capital
India Delhi UPDATE DELETE
USA Washington UPDATE DELETE
----- ------
I would appreciate helping me in achieve this or suggesting a similar type of question.
What you're looking for are Mixins.
Try creating a detail view class with the following parameters:
mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView
For example:
class ObjectDetail(mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
queryset = Object.objects.all()
As has proposed by Daniel, if you like DRF, ViewSets are also a decent alternative. However, they're not exactly succinct so I generally avoid them when possible.
Something like a ModelViewSet, however, is extremely clear-cut and the approach I generally choose.
Here's an example:
class ObjectViewSet(viewsets.ModelViewSet):
queryset = Object.objects.all()
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
Beautiful, isn't it?
For more details, see the DRF tutorial: http://www.django-rest-framework.org/tutorial/6-viewsets-and-routers/
You are mixing view and template. View handle requests and template show content and links.
You will have ListView, which will contain list of posts. In template you add forms for update, form for create and forms for delete. Each form will have attribute action with link to proper view. So update forms will have link to url with UpdateView, create forms to CreateView, and delete to DeleteView. In each form you set redirect back to ListView. This way if you want to use only Django.
OR
If you really want to everything handle on one page without refreshing and redirecting. You can use ajax and django-rest-framework and its viewset. In viewset you can handle lists, create, update, push, detail, in one class.
Viewset:
class UserViewSet(viewsets.ViewSet):
"""
Example empty viewset demonstrating the standard
actions that will be handled by a router class.
If you're using format suffixes, make sure to also include
the `format=None` keyword argument for each action.
"""
def list(self, request):
pass
def create(self, request):
pass
def retrieve(self, request, pk=None):
pass
def update(self, request, pk=None):
pass
def partial_update(self, request, pk=None):
pass
def destroy(self, request, pk=None):
pass
I have a DetailView in django views.py where I want to be able to compare the pk value from the url ex:localhost:8000/myapp/details/3/ and the request.user.id with an if statement.
There is nothing more than the following few lines of code in the view:
class UserDetail(DetailView):
model = Profile
template_name = 'details.html'
Any help would be much appreciated.
Inside a DetailView you have access to self.request, self.args and self.kwargs!
ref.: https://docs.djangoproject.com/en/dev/topics/class-based-views/generic-display/#dynamic-filtering
In your urls.py add something like this:
urlpatterns = [
#...
url(r'^details/(?P<pk>[0-9]+)/$', UserDetail.as_view()),
]
and your UserDetail can now access request.user.id and pk by self.kwargs['pk'] (see reference above: kwargs is name-based, so that you can access it by self.kwargs['name'] and self.args is position-based, so you would access it by self.args[0]).
If I understand your problem correctly, you are trying to manipulate the queryset of the DetailView, to only return the data if the current logged in user is trying to access his page.
If this is true, then you should override get_queryset in your class, like that:
def get_queryset(self):
if self.kwargs['pk'] == self.request.user.id:
return Profile.objects.filter(id=self.request.user.id)
else:
return Profile.objects.none()
Essentially, I'm trying to find a good way to attach more views to a Router without creating a custom Router. What's a good way to accomplish this?
Here is something sort of equivalent to what I'm trying to accomplish. Variable names have been changed and the example method I want to introduce is extremely simplified for the sake of this question.
Router:
router = routers.SimpleRouter(trailing_slash=False)
router.register(r'myobjects', MyObjectViewSet, base_name='myobjects')
urlpatterns = router.urls
ViewSet
class MyObjectsViewSet(viewsets.ViewSet):
""" Provides API Methods to manage MyObjects. """
def list(self, request):
""" Returns a list of MyObjects. """
data = get_list_of_myobjects()
return Response(data)
def retrieve(self, request, pk):
""" Returns a single MyObject. """
data = fetch_my_object(pk)
return Response(data)
def destroy(self, request, pk):
""" Deletes a single MyObject. """
fetch_my_object_and_delete(pk)
return Response()
One example of another method type I need to include. (There are many of these):
def get_locations(self, request):
""" Returns a list of location objects somehow related to MyObject """
locations = calculate_something()
return Response(locations)
The end-result is that the following URL would work correctly and be implemented 'cleanly'.
GET example.com/myobjects/123/locations
The answer given by mariodev above is correct, as long as you're only looking to make GET requests.
If you want to POST to a function you're appending to a ViewSet, you need to use the action decorator:
from rest_framework.decorators import action, link
from rest_framework.response import Response
class MyObjectsViewSet(viewsets.ViewSet):
# For GET Requests
#link()
def get_locations(self, request):
""" Returns a list of location objects somehow related to MyObject """
locations = calculate_something()
return Response(locations)
# For POST Requests
#action()
def update_location(self, request, pk):
""" Updates the object identified by the pk """
location = self.get_object()
location.field = update_location_field() # your custom code
location.save()
# ...create a serializer and return with updated data...
Then you would POST to a URL formatted like:
/myobjects/123/update_location/
http://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing has more information if you're interested!
You can now do this with the list_route and detail_route decorators: http://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing
For example:
from rest_framework.decorators import list_route
from rest_framework.response import Response
...
class MyObjectsViewSet(viewsets.ViewSet):
...
#list_route()
def locations(self, request):
queryset = get_locations()
serializer = LocationSerializer(queryset, many=True)
return Response(serializer.data)
You define method like you do now, but you need to use the same url as method name and add link decorator, so for
/myobjects/123/locations/
You add method like this
#link(permission_classes=[...])
def locations(self, request, pk=None):
...
and router will pick it automatically.
From Routing to extra methods on a ViewSet:
I think you may need to route the method by hand, i.e. The Old-Fashioned Way™.
First pull the method out as a separate view:
set_password_view = UserViewSet.as_view({'post': 'set_password'})
(or such)
Then assign your URL:
url(r'^users/username_available/$', set_password_view, name-=...)
(Or such)
There's a related question on SO.
If you want to extend a viewset with a view that is or should not directly be written inside your viewset, you can write a “wrapper” action to pass the data through.
For example, with class based views:
from somewhere import YourExternalClassView
class SomeViewSet(viewsets.ReadOnlyModelViewSet):
# ...
#action(detail=True)
def your_action(self, request, pk):
return YourExternalClassView.as_view()(request, pk=pk)
How does it work?
On class based views, the as_view method returns a view function, to which we will pass the data we received from the action. The view will then hand over to process further.
For non-class based view, the views can be called/wrapped directly without .as_view(...)(...).
I want to create a "method_splitter" equivalent using class-based views in order to remove some hard-coding in my URL confs.
I would like to have the following URL's:
ListView: http://mysite.com/<model_name>/
DetailView: http://mysite.com/<model_name>/<field_value>/
where the query for the ListView would be:
<model_name>.objects.all()
and the queryset for the DetailView would be:
<model_name>.objects.get(<field>=<field_Value>)
Currently, my views work as a result of some hardcoding in the url conf, but I would like to find an elegant solution that can scale.
My solution does not give a 404, but displays nothing:
views.py
class ListOrDetailView(View):
def __init__(self, **kwargs):
for key, value in kwargs.iteritems():
setattr(self, key, value)
try: #If/Else instead?
def goToDetailView(self, **kwargs):
m = get_list_or_404(self.kwargs['model']) #Is this even needed?
return DetailView(model=self.kwargs['model'], slug=self.kwargs['slug'], template_name='detail.html', context_object_name='object')
except: #If/Else instead?
def goToListView(self, **kwargs):
q = get_object_or_404(self.kwargs['model'], slug=self.kwargs['slug']) #Is this even needed?
return ListView(model=self.kwargs['model'], template_name='list.html', context_object_name='object_list',)
urls.py of MyApp
url(r'^(?P<model>[\w]+)/?(?P<slug>[-_\w]+)/$', ListOrDetailView.as_view()),
As limelights said, this is a horrible design pattern; the whole point of Django is separation of models and views. If you fear that you might have to write a lot of repetitive code to cover all your different models, trust me that it's not much of an issue with class-based views. Essentially, you need to write this for each of your models you wish to expose like this:
Urls.py:
urlpatterns = patterns('',
url(r'^my_model/$', MyModelListView.as_view(), name="mymodel_list"),
url(r'^my_model/(?P<field_value>\w+)/$', MyModelDetailView.as_view(), name="mymodel_detail"),
)
views.py:
from django.views.generic import ListView, DetailView
class MyModelListView(ListView):
model = MyModel
class MyModelDetailView(DetailView):
model = MyModel
def get_queryset(self):
field_value = self.kwargs.get("field_value")
return self.model.objects.filter(field=field_value)