Sucess_url based on pk in GenericView from urls.py - python

In my url.py I have urls like:
url(r'^messstellen/monatlicher_verbrauch/(?P<pk>[0-9]+)/update/$',
generic.UpdateView.as_view(
model=MonatlicherVerbrauch,
form_class=MonatlicherVerbrauchForm,
success_url=reverse('messstellen:messstellen_index'),
template_name='messstellen/monatlich_form.html',
),
now I want to let the success_url be something like:
success_url = redirect('messstellen:messtelle_detail', pk=pk)
where the pk schould be the same like in the regex pattern (?P<pk>[0-9]+)
Is there a way to do it in the url.py view?

If you don't define success_url, then Django will use your model's get_absolute_url method, which you could define as:
class MonatlicherVerbrauch(models.Model):
...
def get_absolute_url(self):
return reverse('messstellen:messtelle_detail', args=[self.pk])
If your get_absolute_url points to a different url, then I don't think it is possible to set the success_url dynamically in the urls. You will have to override the view, and define get_success_url.
class MonatlicherVerbrauchUpdateView(UpdateView):
def get_success_url(self):
return reverse('messstellen:messtelle_detail', args=[self.object.pk])
# define these attributes in the view as well, to keep urls simple
model=MonatlicherVerbrauch,
form_class=MonatlicherVerbrauchForm,
template_name='messstellen/monatlich_form.html',
Then use MonatlicherVerbrauchUpdateView in your urls instead of UpdateView.
url(r'^messstellen/monatlicher_verbrauch/(?P<pk>[0-9]+)/update/$',
MonatlicherVerbrauchUpdateView.as_view()),
The advantage of subclassing the generic view is that it separates the logic of your views from the urls.

Related

One object only in GET method in Django REST Framework

I have a list of all my objects when I use get method by api/movies in my api, and this is ok. I want also to get only one, specyfic object when use get method by api/movies/1 but now I still have a list of all my objects... What to change in my MoviesView or in urls?
My views.py:
class MoviesView(APIView):
def get(self, request):
movies = Movie.objects.all()
serializer = MovieSerializer(movies, many=True)
return Response(serializer.data)
My appurls.py:
urlpatterns = [
url('movies', MoviesView.as_view(), name="MoviesView"),
]
And my project urls.py:
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include("api.urls")),
]
When I use routers everythig crushes... Could you help me?
You can simply use viewsets.ModelViewSet that natively implements list and retrieve.
You declare something like router.register('movies', my_views.MoviesViewSet) in you urls.py and
class MoviesViewSet(viewsets.ModelViewSet):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
permission_classes = [IsAuthenticated, ]
def get_queryset(self):
return self.queryset
def get_object(self):
movie_id = self.kwargs['pk']
return self.get_queryset().filter(id=movie_id)
def retrieve(self, request, *args, **kwargs):
try:
instance = self.get_object()
except (Movie.DoesNotExist, KeyError):
return Response({"error": "Requested Movie does not exist"}, status=status.HTTP_404_NOT_FOUND)
serializer = self.get_serializer(instance)
return Response(serializer.data)
def list(self, request, *args, **kwargs):
queryset = self.get_queryset()
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
This approach implies that you declare a Serializer, just like:
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = '__all__'
Django simply maps HOST/movies/ to list (multiple objects) and HOST/movies/PK/ to retrieve method (one single object).
Docs:
https://www.django-rest-framework.org/api-guide/viewsets/#modelviewset
https://www.django-rest-framework.org/api-guide/serializers/#modelserializer
Hope it helps.
BR.
Eduardo
I would suggest you if you want to retrieve just 1 element to use a Generic View, i.e RetrieveAPIView
It would give you all you need for getting 1 element.
from rest_framework import generics
class MoviesView(generics.RetrieveAPIView):
queryset = Movie.objects.all()
serializer_class = MovieSerializer
but you need also to change urls.py
url(r'movies/(?P<pk>[0-9]+)/$', MoviesView.as_view(), name="MoviesView"),
When you make a GET request to "api/movies/1", the url is matched to the "api/movies" path (read more in the docs), and the MoviesView's get method is called. And your get() implementation just fetches all the movies (movies = Movie.objects.all()), serializes and returns them - that's why you get the entire list.
If you want to retrieve one specific object, you need to somehow specify which object you have in mind, using its primary key (in your case, id).
1. You have to define a separate path: movies/<int:pk>/ (btw, which Django version are you using? url has been deprecated, use path instead!)
2. You have to define a detail view to handle this new case, and pass it to the path function as the second argument.
This general problem can really be solved in many ways, and depending on your app you may want to use a ViewSet instead of views. Then you don't have to define paths (urls) separately - you can use a router. You can't use routers with your view, because router needs a viewset class as its argument.
If you provide more details, I could try to suggest something more specific.
My appurls.py:
use path method
urlpatterns = [
path('movies', MoviesView.as_view(), name="MoviesView"),]
Maybe it works
Start by adding a format keyword argument to both of the views, like so
def snippet_list(request, format=None):
and
def snippet_detail(request, pk, format=None):
Now update the snippets/urls.py file slightly, to append a set of format_suffix_patterns in addition to the existing URLs
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views
urlpatterns = [
path('snippets/', views.snippet_list),
path('snippets/<int:pk>', views.snippet_detail),
]
urlpatterns = format_suffix_patterns(urlpatterns)

Additional views to Django Rest Framework ViewSet

I've got simple DRF ViewSet for a model, located at /gen_req/
class GenerationRequestViewSet(viewsets.ModelViewSet):
queryset = GenerationRequest.objects
serializer_class = GenerationRequestSerializer
It has default POST/GET/etc. handlers. However, I want to add another one for GET as well for different url patter (/gen_req/created_list:
class GenerationRequestViewSet(viewsets.ModelViewSet):
queryset = GenerationRequest.objects
serializer_class = GenerationRequestSerializer
#action(methods=['get'])
def special_get_handler(self, request):
queryset = GenerationRequest.filter(...) # Some extra filtering here
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
Is there a way to create such view within the ViewSet or another APIView should be created?
You can do it, just add such record to your urls.py file.
path('/gen_req/created_list',
GenerationRequestViewSet.as_view({'get': 'special_get_handler'}),),
You can do that, but you need to name your method accordingly. So with code, you've posted you will be able to get this method by requesting /gen_req/special_get_handler.
Of course, it should be registered in url.py. Smth like:
api_router = DefaultRouter()
api_router.register("gen_req", GenerationRequestViewSet)

How to change template names in django listview according to url request?

I have 2 templates to render one listview and I am choosing the template according to the request url given by the user. I know that, I can add 2 classes for 2 templates on 2 seperate urls respectively. For example
class MyListView1(generic.ListView):
template_name = 'myapp/list_one.html'
.....
.....
class MyListView2(generic.ListView):
template_name = 'myapp/list_two.html'
.....
.....
But is there a way if I could check the url request inside one class and render the template according to it inside one listview class ? something like
class MyListView(generic.ListView):
if request.path == '/list1'
template_name = 'myapp/list_one.html'
if request.path == '/list2'
template_name = 'myapp/list_two.html'
I know this is not a valid code but just to visualise
Whenever you want to do something dynamic in a generic view, it needs to be in a method. This page shows the methods available for ListViews, and you can see that it includes get_template_names() which should do exactly what you want.
An alternative though would be to have two separate view classes, each defining their own template name, that inherit from a common base class which defines the rest of the shared functionality.
Just pass template from urls.py like
path("/list1",views.MyListView.as_view(template_name="myapp/list_one.html"),name="list1")
path("/list2",views.MyListView.as_view(template_name="myapp/list_two.html"),name="list2")

Passing 'success_url' to CBV Django-Registration

I'd like to pass a success_url to the class-based ActivationView in Django Registration like this answer covers for function-based views and this answer covers for RegistrationView. What I have tried so far that has been unsuccessful:
url(r'^activate/(?P<activation_key>\w+)/$',
ActivationView.as_view({'success_url':'/activation_routing'}),
name='registration_activate',
),
returns "TypeError: as_view() takes exactly 1 argument (2 given)" I have also tried
and:
url(r'^activate/(?P<activation_key>\w+)/$',
ActivationView.as_view(success_url='/activation_routing'),
name='registration_activate',
),
returns "TypeError: ActivationView() received an invalid keyword 'success_url'. as_view only accepts arguments that are already attributes of the class."
I feel like I'm missing something with class-based views, or is subclassing ActivationView and putting in custom logic my best bet?
You can indeed only pass existing attributes to as_view(). As such, and looking at the source of django-registration, the view doesn't have a success_url attribute but it obtains its value by calling self.get_success_url(...).
By default this method is not implemented so you have little choice besides subclassing ActivationView and implementing get_success_url yourself.
I think you have to subclass the view and override the get_success_url method.
I opened pull request 57 to enable setting success_url as a class attribute, but it has not been merged yet.
As others confirmed, I was able to resolve this by subclassing ActivationView and overriding the get_success_url() and activate() methods:
"""
views.py
"""
from registration.views import ActivationView
class CustomActivation(ActivationView):
def activate(self, request, *args, **kwargs):
return True
def get_success_url(self, request, user):
success_url = # insert success URL here
return success_url
It's also important to set the correct URL in your urls.py file to override the default ActivationView that will be called by django-registration. One quirk to remember is that django-registration will set its URLs according to the filepath of auth.urls and not what you specify in your app's urls.py file:
"""
urls.py
"""
from yourapp.views import CustomActivation
urlpatterns += patterns('',
url(r'^user_settings/', include('auth.urls')),
url(r'^user_settings/activate/(?P<activation_key>\w+)/$',
CustomActivation.as_view(),
name='registration_activate',
),
# will still set registration URLs under user_settings!
url(r'^accounts/', include('registration.backends.default.urls')),
)

passing data between class based forms

I am fairly new to Django and class based forms, and I am having trouble understanding how these interact with each other. Following from the django project example, I have tried to build a "search form", which would sit on all pages of my project:
# forms.py
from django import forms
class SearchForm(forms.Form):
myquery = forms.CharField(max_length=255,label="", help_text="sq")
def __unicode__(self):
return self.myquery
# views.py
from searchapp.forms import SearchForm
from django.views.generic.edit import FormView
from django.views.generic import TemplateView
class SearchView(FormView):
template_name = 'index.html'
form_class = SearchForm
success_url = '/searchres/'
def form_valid(self, form):
thequery=form.cleaned_data.get('myquery')
return super(SearchView, self).form_valid(form)
class Meta:
abstract = True
class SearchResView(SearchView):
template_name = 'searchres.html'
#urls.py
from django.conf.urls import patterns, include, url
from django.conf import settings
from deals.views import IndexView
from searchapp.views import SearchView, SearchResView
urlpatterns = patterns('',
url(r'^index/', SearchView.as_view(),name="home"),
url(r'^searchres/', SearchResView.as_view(),name="searchresx"),
)
The plan is the start off with a simple form for user to enter the search query, and also show the input form on the results page. I have the following questions here (sorry - I am a Django newbie esp. to Class Based Views):
How does one pass data ("thequery") to the success_url? i.e I would like success_url to have access to "thequery" so that I can use something like {{thequery}} on my template tags.
Upon submitting the form(name="home"), I see POST data from the form on my firebug, but I am able to see just "myquery" rather than "thequery". How does one use get_context_data() here to add/post "thequery" variable aswell?
Finally, I was wondering if it would be possible to construct the success_url based on "thequery" string i.e something like success_url = '/searchres/?q=' + thequery
Thank you in advance - I am hoping to learn more.
I would suggest using function based views for this. If you choose to subclass a generic view you will need to dig through a lot of documentation and possibly source code, to find the right methods to override. (If you're really keen then look at the ListView class along with the get_queryset(), get() and post() methods)
A single django view will normally handle both rendering the empty form AND processing the submitted form.
So the search page (both the form and the results), live at http://your-site.com/search. Your url conf is -
urlpatterns = patterns('',
#...
(r'^search/$', 'searchapp.views.search'),
)
And your view looks something like this -
def search(request):
if request.method == 'POST':
form = SearchForm(request.POST)
if form.is_valid():
my_query = form.cleaned_data['myquery']
object_list = YourModel.objects.filter(# some operation involving my_query)
return render_to_response('search_results.html', {'object_list': object_list})
else:
form = SearchForm()
render_to_response('search_form.html', {'form': form})
(Note I've assumed your form method is post rather than get - I know this isn't great http but it's a common pattern with django)
To respond to your questions -
Don't use your own method for cleaning data. Add a clean_myquery method to your form and access it with form.fields['myquery'].clean() (or if you've called is_valid() on your form, it's accessible with just form.cleaned_data['myquery']).
You want to try and avoid passing data for processing to the template. Do as much processing as you can in the view, then render the template. However if you want to pass myquery as a string for the template to render, then add it in to the context dictionary (the second non-key-word argument) in render_to_response -
return render_to_response('search.html', {'object_list': object_list, 'myquery': my query})
The post data is constructed from the form fields. You don't have a form field thequery. The view is processing the POST data - it's not creating it that's done by the html (which in turn is constructed by the Form class). Your variable thequery is declared in the view.
Django's URL dispatcher ignores query strings in the URL so http://your_site.com/ssearch will be processed by the same view as http://your_site.com/search?myquery=findstuff. Simply change the html in the template from <form method='post'> to and access the data in django with request.GET. (You'll need to change the code from the view I described above to include a new check to see whether you're dealing with a form submission or just rendering a blank form)
Have a good read of the docs on views, forms and the url dispatcher.

Categories