I am using Pyramid with different views. I am wondering if it is possible to "delegate" parts of a views job to another view (another route).
For example:
http://localhost:6543/sample_project/testruns/testrun001/report.html?action=edit
=> delegate to:
http://localhost:6543/sample_project/testruns/testrun001/report.json
the views I am using:
# report:
#view_config(context=Root, route_name='report_route')
def report_view(context, request):
...
if 'edit' in request.GET.getall('action'):
# TODO: delegate to code_view
???
...
# render report from report.json
# editor:
#view_config(context=Root, route_name='report_edit_route')
#view_config(context=Root, route_name='code_route')
def code_view(context, request):
....
You can directly call views, they simply won't be going through the pyramid router mechanism which applies the permission and other such parameters to the view. Presumably if you are trying to call it, however, you already know these things.
In reality, you probably just want to refactor the common functionality into a separate function that each of your views can then delegate part of the work to.
Related
Background: I would like to enhance a page instance during an admin page view with some admin request related information (some pre-population in general). Basically I would need some function like "get_queryset", but not for list view, just for edit view.
In my older question related to a similar problem: Wagtail - how to preopulate fields in admin form? I was provided with instructions to use something called
CreatePageView
However, I cannot import it. Furthermore, I cannot even found any mention about that in google if I search:
Wagtail +CreatePageView
The closest thing I found is https://docs.wagtail.io/en/v2.1.1/reference/contrib/modeladmin/create_edit_delete_views.html but the page also states:
NOTE: modeladmin only provides ‘create’, ‘edit’ and ‘delete’
functionality for non page type models (i.e. models that do not extend
wagtailcore.models.Page). If your model is a ‘page type’ model,
customising any of the following will not have any effect
I am quite confused. What should I do if I need to customize the admin view for Page model extension?
I studied the wagtail source codes for Model.admin and Page and I have not found any way. Any ideas?
The related code simplified:
wagtail hooks:
class ItemAdmin(ModelAdmin):
pass
# some function override here maybe?
models:
class ItemPage(Page):
pass
# override for a function that gives data to the admin view maybe here?
Edit
As suggested in comments, it is possible to modify the admin page form during creation:
from wagtail.admin.forms import WagtailAdminPageForm
class ItemPageForm(WagtailAdminPageForm):
def __init__(self, data=None, files=None, parent_page=None, *args, **kwargs):
super().__init__(data, files, *args, **kwargs)
class ItemPage(Page):
base_form_class = ItemPageForm
however, acquiring the "request" in the WagtailAdminPageForm constructor does not seem possible.
This question is a bit ambiguous, so it is not super clear exactly what you need.
Interpreted question: When crediting (or editing) a page, I need access to the request to modify the initial values of some fields in the page form.
Potential Approach
Note: This may not be best practice and could be fragile depending on future changes to Wagtail.
First, we need a custom EditHandler, these are the way Wagtail builds up forms and even Panels within the editing interface. An EditHandler's job is to manage the form to return based on the model and even the current request.
As a first step, it would be good to get your page create form showing correctly by following the instructions on using a custom tabbed interface. From here, you can replace the TabbedInterface with your custom class (e.g. CustomTabbedInterface) and add some functionality to this which will allow for a dynamic form_class to be returned.
get_form_class should return the form_class, however, we can modify it to return a function that, when called, will instantiate the class with custom information based on the request.
There may be some issues with this approach below in edit views or scenarios not considered by this example so validate this fully before using.
Example Code
from wagtail.admin.edit_handlers import TabbedInterface, ObjectList
from wagtail.core.models import Page
class CustomTabbedInterface(TabbedInterface):
def get_form_class(self):
form_class = super().get_form_class()
request = self.request
if request and request.method != 'POST':
# check request is available to ensure this instance has been bound to it
user = self.request.user
def initiate_class(**kwargs):
# instead of returning the class, return a function that returns the instantiated class
# here we can inject a kwarg `initial` into the generated form
# important: this gets called for edit view also and initial will override the instance data
# kwarg['instance'] will be the `Page` instance and can be inspected as needed
kwargs['initial'] = {'introduction': user.first_name}
return form_class(**kwargs)
return initiate_class
return form_class
class StandardPage(Page):
# ... field etc
edit_handler = CustomTabbedInterface([
ObjectList(content_panels, heading='Content'),
ObjectList(Page.promote_panels, heading='Promote'),
ObjectList(Page.settings_panels, heading='Settings', classname="settings"),
])
Explanation
wagtail/admin/views/pages.py contains the create view, which will use the edit_handler, bind it to the model and the request and then call its get_form_class.
The form_class is used for the response here form = form_class(instance=page, parent_page=parent_page)
It gets called with the instance and the parent_page kwargs
Our custom get_form_class response takes those kwargs and injects an additional initial kwarg.
initial is used by Django forms to add any initial data - https://docs.djangoproject.com/en/3.0/ref/forms/api/#dynamic-initial-values
Finally, the Django form will merge the instance field values with the intial kwarg to generate the final pre-filled data for the form. You can see how this works in Django's BaseModelForm.
Be careful to consider what will happen on an update view, you likely do not want to override existing values with your initial values when a user has already entered something in the field.
I am working on a search filter of an ecommerce site.
Current Situation:
When user select each platform filter, the platform name will be appended to URL and display the filtered result.
My approach:
url.py
url(r'^search/(?P<product_slug>[0-9a-z-]+)$', CustomSearchView(), name='search_result_detail'),enter code here
url(r'^search/(?P<product_slug>[0-9a-z-]+)_(?P<platform_slug>[0-9a-z-]+)$', CustomSearchView(),
name='search_result_platform'),
url(r'^search/(?P<product_slug>[0-9a-z-]+)_(?P<platform_slug>[0-9a-z-]+)_(?P<platform_slug2>[0-9a-z-]+)$',
CustomSearchView(), name='search_result_platform2'),
url(r'^search/(?P<product_slug>[0-9a-z-]+)_(?P<platform_slug>[0-9a-z-]+)_'
r'(?P<platform_slug2>[0-9a-z-]+)_(?P<platform_slug3>[0-9a-z-]+)$',
CustomSearchView(), name='search_result_platform3'),
Main Question:
I didn't want to limit the filtering number. So if there are 20 platform filters, I need to create 20 URLs. Definitely it's not a smart way. Any other smart way to avoid creating a batches of URL?
views.py
def __call__(self, request, product_slug, platform_slug=None,platform_slug2=None,platform_slug3 = None
,platform_slug4 = None,platform_slug5 = None):
if platform_slug is None:
self.product_review_list = SearchResult.objects.filter(products__slug=product_slug)
else:
self.product_review_list = SearchResult.objects.filter(Q(products__slug=product_slug),
Q(platform__slug=platform_slug)|(Q(platform__slug=platform_slug2)|Q(platform__slug=platform_slug3)
|Q(platform__slug=platform_slug4)|Q(platform__slug=platform_slug5)))
As mentioned in the comments, this kind of filtering is a job for GET parameters, not paths.
The URL pattern should just be r'^search/$', and you call it via /search/?platform_slug=x&platform_slug=y&platform_slug=z. Then, in the view, you can just use __in to filter:
def custom_search_view(request):
product_review_list = SearchResult.objects.filter(products__slug__in=request.GET.getlist('platform_slug')
One other point. You must absolutely not use classes like this in your URLs and then set attributes on self. That is not threadsafe and will cause all sorts of problems. If you really need to use a class, subclass the generic View class and use it in the URLs as CustomSearchView.as_view(), then override get() or post().
I would like almost every View in my Django project to compute, for instance, time_on_mars. This variable needs to be put into every template context, so every template can draw time_on_mars. I'd also like every View to be able to use that variable if it wishes, maybe to compute is_it_nighttime_on_mars or whatever. I'd like to do this using inheritance, or some other mechanism, so when I provision a whole lot more than mars time, I don't repeat myself.
Is this an appropriate use of class-based Views? I guess I'd create a base class view, and in its get and post methods, calculate the current time on mars, then call child class methods 'doGet' or 'doPost', intercept the result, put the mars time into the result, and continue. Seems tedious.
Decorators? Is there a way I can put time_on_mars into the closure context for the view?
What's the pythonic (djangonic?) way to do this?
EDIT: I like the idea of context processors, but I've changed my mind - instead of every view, I'd like most views to get this. Especially if it's expensive to compute the time on mars...
EDIT 2: Problem description not adequate! To clarify:
I have a bunch of views that do this:
def some_view(request):
w,x,y,z = provision_wxyz()
... some biz logic, maybe compute a,b,c using w,x,y,z ...
return render(request, 'some_template.html', { 'a':a, 'b':b, 'c':c, 'w':w, 'x':x, 'y':y, 'z':z })
... and I'd like to abstract out the first step (w,x,y,z=), and the adding of w,x,y,z to the template context. Context processors don't work, because inside the some_view business logic, I want access to w,x,y,z. I don't like doing the mixing strategy for CLA on the TemplateView, because again, it doesn't give me access to w,x,y and z inside some_view (or whatever the equivalent of some_view is), and I want to, well, do a bunch of business logic somewhere, and TemplateView doesn't seem to give me that?
You can definitely use CBV's for this. It is a great use for Mixins which take advantage of pythons multiple inheritance. Here is a quick and dirty example I just wrote out.
class MarsMixin(object):
time_on_mars = 5
def get_time_on_mars(self):
"""
Does what it takes to return a time on mars be it calculation
or returning a property set on the object. Should return a property
from the object if it is a constant. Should calcualte in the method
if it is going to be dynanic
"""
return self.time_on_mars
def get_context_data(self, **kwargs):
context = super(MarsMixin, self).get_context_data(**kwargs)
context['time_on_mars'] = self.get_time_on_mars()
return context
class HomeView(MarsMixin, TemplateView):
template_name = 'home/index.html'
Biggest notes are the mixin inherits from object. The other is you inherit from mixin in the HomeView and it is listed before the TemplateView.
I think you can still use context processors, the tip that can help is to prefix the urls where you want to do your calculation, for example they will start by '/mars/...'. You can also use another kind of prefix.
Like this you can check on the context processor method:
def go_to_mars(request):
if request.path.startswith('/mars/'):
return {'time_on_mars': calculate_time_on_mars()}
return {}
I've decent experience in Function based view in Django and now I'm trying to use Class based View. although I'm able to solve problems but I'm not sure about standard, I'mean if I'm doing it right or wrong, what you guyz(Django developers) follow.
More details about problem is here-
views.py
from django.views.generic import View
class InvoiceTransaction(View):
def __init__(self):
super(InvoiceTransaction, self).__init__()
#method_decorator(csrf_exempt)
def dispatch(self, *args, **kwargs):
return super(InvoiceTransaction, self).dispatch(*args, **kwargs)
def get(self, request, *args, **kwargs):
invoiceid = kwargs.get('invoiceid')
# here I have invoiceid, which is I'm passing through url paramaeters(see urls.py file)
# based on invoice, I can decide what type of GET requests it is
# whether user is asking for a single resource or all resource, right?
if invoiceid:
invoice = [Invoice.objects.get(id=invoiceid)]
else:
invoice = Invoice.objects.all()
def post(self, request, *args, **kwargs):
# some stuff
urls.py
from django.conf.urls import patterns, url
from invoice import views
urlpatterns = patterns('',
(r'^invoices/$', views.InvoiceTransaction.as_view()),
(r'^invoices/(?P<invoiceid>.*)/$', views.InvoiceTransaction.as_view()),
)
I followed this tutorial https://realpython.com/blog/python/django-rest-framework-class-based-views/
So my problem is that I'm creating two lines(urls) in urls.py file for a single request to determine the type of GET request. is there any other or better way to do this. how can I create a restful api using view and without creating 2 urls.
PS: feel free to suggest improvement/changes in above code as I'm newbie to this thing. it could be if I'm using dispatch method wrongly or idont really need init method, anything you suggest.
Try this pattern, this should work for both urls -
urlpatterns = patterns('',
(r'^invoices(/(?P<invoiceid>\d+)){,1}/$', views.InvoiceTransaction.as_view()),
)
This makes the whole id block optional but only one can be there at most.
BTW, .* is not quite right for id, better to use \d+
You can verify at pythex.org.
EDIT: with ^invoice(/(?P<invoice_type>.*)){,1}/$
You just have to remove the first pattern and remove the ending slash from the second pattern.
So, your urlpatterns become
urlpatterns = patterns('',
(r'^invoices/(?P<invoiceid>.*)$', views.InvoiceTransaction.as_view()),
)
This is because you are already using .* in the regex instead of .+, invoiceid that you receive can be empty.
But the compulsory end slash in your regex is preventing that from happening. In simpler terms, you already had the solution to your problem.
As REST states that the system must talk only in terms of nouns and verbs.
The verbs that are applicable in a RESTful architecture are:
GET
POST
PUT
PATCH
DELETE
And the 2 urls, that any resource representation for a RESTful system are :
invoice/$
invoice/(?P<pk>\d+)/$
Let's have a look at the View which handles the urls without the pk.Let's call this view InvoiceListCreateView. Now, InvoiceListCreateView should only cater to GET and POST requests. It should not allow PUT, PATCH and DELETE requests. Why?
You can edit/delete only the existing objects and all the existing objects in your db ought to have an id associated with them. So, when I GET the ListCreateView, I get the list of all the Invoices existing in the database. When I POST to the ListCreateView , I should be able to insert a new entry into the Invoice table of the database.
Now, the url with the id appended to it. Let's call it RetrieveUpdateDeleteView. As the name suggests, the view should be able to perform the 3 functions namely, RETRIEVE, UPDATE and DELETE. And all these operations can be performed on the already existing objects for which the id will be present in the url kwargs.
When I GET the RetrieveUpdateDeleteView, I must get the object for that id provided in the url kwargs. Similarly, PUT and PATCH should update the object with the data sent by the user. And DELETE should delete the object from the database.
This is how I plan out the REST architecture.
I set up my function like this
#view_config(
route_name = 'route_name',
permissions = 'permissions',
renderer = 'r.mako'
)
def r( request ):
# stuff goes here
now, I want to add functionality such that I check certain conditions (using ajax) i would use one template, otherwise use another. is there a way to do this in pyramid? thanks
Well you can add the view multiple times with different renderers if you can determine what you want to do via predicates. For example
#view_config(route_name='route', xhr=True, renderer='json')
#view_config(route_name='route', renderer='r.mako')
#view_config(route_name='route', request_param='fmt=json', renderer='json')
def r(request):
# ...
Or you can just override the renderer manually via request.override_renderer = 'b.mako':
http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/narr/renderers.html#overriding-a-renderer-at-runtime
Or you can just explicitly render the response via the render and render_to_response methods from within the view, as the renderer argument is ignored if you return a Response object from the view.
Note that the xhr predicate in the first example should be sufficient to check for an ajax request. Note also that you don't have to use the same view for both if you don't want to, just depends.