I'm looking for ways of adding custom actions to a Django admin change page. I am not looking for actions that can be added to the dropdown box in the overview.
One model in my Django application contains information about documents, and those are automatically compiled to PDF files at the frontend. I'd like to give the administrator the possibility to quickly render the PDF directly from the change page to be able to quickly check the results.
I've already played around with overriding change_form.html/submit_line.html, and it's absolutely no problem to add a button. But I wonder how to extend the admin module in a clean way so that it includes my custom action.
Since custom admin views are basically just normal views there aren't many specialities. A few things you should consider for a cleaner integration:
Add the url pattern through get_urls() of the AdminSite.
Provide the current_app argument to RequestContext or Context when rendering a template which subclasses admin templates.
EDIT:
Found a an example here:
http://shrenikp.webs.com/apps/blog/show/5211522-add-custom-view-method-for-django-admin-model-
Note, that the example doesn't utilize the current_app argument i've mentioned. I suppose your view to generate the PDF just returns a HttpResponse with the appropriate content type rather than rendering a response with a Context, so it's not needed. All in all the current_app only makes sense when you subclass an admin template used by your custom view which actually makes use of current_app somewhere.
The example encapsulates the urls and view in a ModelAdmin. It is also possible to do this through a AdminSite subclass, but at least in your use case this is probably overkill.
By the way, overriding the change_form.html template for your app to add a button to the standard change view is just fine. There is no special api for this in the admin (unfortunately).
Related
I have a view inheriting from LoginRequiredMixin, TemplateView, which renders some data using the admin/base_site.html template as the base. I treat it as a part of the admin interface, so it requires an administrator login. I'd like to make this view a little bit more a part of the Django admin interface by enabling the standard sidebar navigation on the left-hand side.
Note that I don't have a custom ModelAdmin definition anywhere, I simply render the template at some predefined URL. There are no models used in the interface either, it parses and displays data from database-unrelated sources.
Currently, I just build the required context data manually, e.g.:
data = super().get_context_data(**kwargs)
data.update(**{
"is_popup": False,
"site_header": None,
"is_nav_sidebar_enabled": True,
"has_permission": True,
"title": "My title",
"subtitle": None,
"site_url": None,
"available_apps": []
})
The sidebar is visible, but displays an error message:
Adding an app to available_apps ("available_apps": ["my_app"]) doesn't help either:
So my question is - how do I do that? Is there a class I can inherit from to achieve this behaviour? Or a method I can call to get all required context data for base_site.html? Or perhaps I should insert some information in my template? Perhaps I need an AdminSite object, or can somehow call methods of the default one?
By accident I noticed there is a DefaultAdminSite object in django.contrib.admin.sites, and it's instantiated as site. Therefore, in my case, simply using site is sufficient.
from django.contrib.admin import AdminSite
from django.contrib.admin.sites import site
admin_site: AdminSite = site
data.update(**admin_site.each_context(self.request))
Furthermore, turns out I can just use the apps in case importing site would be an issue, just like DefaultAdminSite does it:
AdminSiteClass = import_string(apps.get_app_config("admin").default_site)
self._wrapped = AdminSiteClass()
The app_list.html template which is included in your base template requires a context variable available_apps with a structure as described here.
As you probably won't be able to make this include work properly with your app you could also override the {% block nav-sidebar %} in your base template and provide some HTML that fits your use-case.
But after all it's probably not such a good idea to reuse templates which are made for a different app (admin) with your app because it's difficult to predict how they will behave, what their requirements are and beyond this maintainance in the long term will also be a problem as they might change with future Django versions.
You have to include django admin's default context variables used in the template. This can be done by calling each_context function:
data.update(self.admin_site.each_context(request))
I also have custom admin views and templates in my projects and this works well for me.
I'm a beginner to web development in django, and I'm wondering why views needed to be rendered with functions and classes? Can't the urls module just link the url to the HTML template directly rather than indirectly through a function-based view?
You can indeed have the urls module just render the HTML for you right way. For that, you can use direct_to_template: https://django.readthedocs.io/en/1.4.X/topics/generic-views.html#using-generic-views
You would rely on a function when you have any extra processing to be done before sending the response ("rendered template") to the user. You could need to log the user's IP address, for example, or you could load data from a database to fill in the template. Or you can not even have to render a HTML, but a JSON instead. That's why you would need a custom view, which would be implemented in either a function or class.
So the point of Django in general is you're going to want to be serving more than just static HTML, you'll probably want to process that in some way. As other answers have said, if you just want to return HTML, you can use a TemplateView (https://docs.djangoproject.com/en/3.0/ref/class-based-views/base/#templateview) or just have your web server server the static files (https://docs.djangoproject.com/en/3.0/howto/static-files/deployment/)
Unless your use case is a single page app, its highly likely you'll have some HTML that's common to multiple pages, in which case you can include and extend templates.
We have a large Django application made up of a large number of different views (some of which contain forms). As with most large applications, we use a base layout template that contains the common layout elements across the applications (mainly a header and a footer), which the templates for all of our views extend.
What we are looking to do is create a universal search box in our application, accessible on every page, which allows users to perform searches across the entire application, and want to place the search box inside the header, which involves placing a form inside our base layout template. This means that every view in our application will need to be able to handle the submission of this search form. Once this search form is submitted, we will need to redirect the user to another view containing the search results.
However, we are struggling to come up with a pattern to handle this. Does anyone know of functionality built into Django that will help us to build this? Failing that, can anyone suggest a good strategy for modifying our application so that we can handle this use-case without having to modify a large number of existing views (which we don't have the resources to do at the moment)?
Please note that the focus of this question is intended to be the best way to handle the submission of a form which appears in every view, and not strategies for implementing a universal search algorithm (which we have already figured out).
Ideas Explored So Far
Our first idea was to create a base View class that implements handling the universal search form submission, and have each of our views extend this. However, this is not possible because we already have views that inherit from a number of different Django view classes (TemplateView, ListView, FormView and DeleteView being some examples), and to be able to build our own common view class would mean either writing our own version of these Django view classes to inherit from our own view base class, or re-writing a large number of our views so they don't use the Django view classes.
Our next idea was to implement a mixin that would handle the universal search form submission, in an attempt to add this functionality to all our views in a way that allows us to continue using the different Django view classes. However, this brought to light two new problems: (a) how could we do this without modifying each of our views to become a form view, and (b) how can we do this in a way that allows the form handling logic to play nicely when mixed in to existing FormViews?
This seems like such an obvious question that maybe I'm overlooking something. But as others have said your universal search form should not make a POST request to the view that rendered the current page.
Each html form has an action attribute. The attribute of your search form should point towards an URL. Probably something like /search. That url would have a view behind it that handled the POST request from the form and returned the search results. Django has URL template tags to make this easy. {% url 'myapp.views.search' %} will give you the correct url for the search view function if it lived inside the views module in myapp. So the relevant bit of html in your base template would be something like:
<form action="{% url 'myapp.views.search' %}">
<input type="text" name="qs" placeholder="Search">
</form>
If you are planning on displaying the search results on a new page there is absolutely no need to return JSON or anything like that. Just have a search view that looks like this
def search(request):
query = request.POST.get('qs', '')
results = SomeModel.objects.filter(name=query) # Your search algo goes here
return render(request, 'search_results.html', dict(results=results))
Instead of handling the form submission on every view of the application, you can implement a separate view (endpoint), which handles all the search queries. (an endpoint which returns JSON result) since you dont want to add overhead of rendering the whole page with that view. So the search query (which client side AJAX performs to the webserver) will return JSON response, and the Javascript can render that response. This way you can keep the search view isolated from the rest of the views. (Django REST will be helpful in this case)
And this search form will be included in your base template, so your search box is accessible from the entire application, and it submits to the same view. And the AJAX function will handle the server response for rendering it.
It seems like you just need to create another SearchView which takes the query and displays the results. I am not sure if the results have to be displayed differently depending on which page the search has been performed from but it does not seem like.
The form would not have anything to do with the other views. You could just hard code it in the base template.
I've noticed that after I log in with Django registration it redirects me to accounts/profile/. By default Django registration's url.py doesn't handle accounts/profile/, so I need to create my own.
Actually this questions is three-fold:
Why does after logging in, it redirects to accounts/profile/? Is there a way to change that? Preferably after successfully logging in I would like Django to redirect back to the page before the login page.
If I were to create my own view and template for accounts/profile/, then where should I put it? Django's built-in users (auth_user) is shared among all Django apps inside a project, so should I place the view.py in the project folder and not inside the app folder?
Or does Django profile actually takes care of this whole account/profiles/ thing? I already extended Django's User class with my own UserProfile, but it's more like additional fields to the User table than an actual "profile" (I didn't create avatars or anything like that, just simple stuff like addresses and phone numbers, but most importantly, some custom user types that my app depends on).
Why does after logging in, it redirects to accounts/profile/? Is there
a way to change that? Preferably after successfully logging in I would
like Django to redirect back to the page before the login page.
Just change setting LOGIN_REDIRECT_URL
If I were to create my own view and template for accounts/profile/,
then where should I put it? Django's built-in users (auth_user) is
shared among all Django apps inside a project, so should I place the
view.py in the project folder and not inside the app folder?
I like to create an app called "project_specific" in every project. That's where I put all the stuff that's not meant to be reusable and that couples many apps.
You can also create a views.py at the project level, but that is sort of messy compared to making a project specific app.
In reality it does not matter where you put it.
Or does Django profile actually takes care of this whole
account/profiles/ thing? I already extended Django's User class with
my own UserProfile, but it's more like additional fields to the User
table than an actual "profile" (I didn't create avatars or anything
like that, just simple stuff like addresses and phone numbers, but
most importantly, some custom user types that my app depends on).
That's not the way to add extra user fields. I recommend that you read the docs on Storing additional information about users.
For a minimal approach that doesn't require a standalone app,
Create a template and call it profile.html or anything you want.
<p>This is your profile, {{ user.username }}.</p>
In urls.py, add a url pattern that points to your profile template, mark it login_required, and give the url a name:
# ...
from django.views.generic import TemplateView
from django.contrib.auth.decorators import login_required
urlpatterns = [
# ...
url(r'^accounts/profile/$', login_required(TemplateView.as_view(template_name='profile.html')), name='user_profile'),
# ...
]
In settings.py, add the following line:
LOGIN_REDIRECT_URL = 'user_profile'
This line tells Django to perform a reverse URL lookup by name when redirecting a user after a login. Without this line, your app will still work but it will be fragile because it relies on an arbitrary hard-coded URL that is implicitly configured by Django. With this line, if you or someone else decides that user profiles should be at /me/, you could change the URL in step 2 without breaking your app.
Set LOGIN_REDIRECT_URL in settings - https://docs.djangoproject.com/en/dev/ref/settings/#login-redirect-url
Create account app, where contains code for this.
You may use django userena for full-stack user area: https://django-userena.readthedocs.org/en/latest/
I have a Django website as follows:
site has several views
each view has its own template to show its data
each template extends a base template
base template is the base of the site, has all the JS/CSS and the basic layout
So up until now it's all good. So now we have the master head of the site (which exists in the base template), and it is common to all the views.
But now I want to make it dynamic, and add some dynamic data to it. On which view do I do this? All my views are basically render_to_response('viewtemplate.html', someContext). So how do add a common view to a base template?
Obviously I will not duplicate the common code to each separate view...
I think I'm missing something fundamental in the MVT basis of Django.
You want to use context_instance and RequestContexts.
First, add at the top of your views.py:
from django.template import RequestContext
Then, update all of your views to look like:
def someview(request, ...)
...
return render_to_response('viewtemplate.html', someContext, context_instance=RequestContext(request))
In your settings.py, add:
TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.auth',
...
'myproj.app.context_processors.dynamic',
'myproj.app.context_processors.sidebar',
'myproj.app.context_processors.etc',
)
Each of these context_processors is a function takes the request object and returns a context in the form of a dictionary. Just put all the functions in context_processors.py inside the appropriate app. For example, a blog might have a sidebar with a list of recent entries and comments. context_processors.py would just define:
def sidebar(request):
recent_entry_list = Entry.objects...
recent_comment_list = Comment.objects...
return {'recent_entry_list': recent_entry_list, 'recent_comment_list': recent_comment_list}
You can add as many or as few as you like.
For more, check out the Django Template Docs.
Context processors and RequestContext (see Tyler's answer) are the way to go for data that is used on every page load. For data that you may need on various views, but not all (especially data that isn't really related to the primary purpose of the view, but appears in something like a navigation sidebar), it often makes most sense to define a custom template tag for retrieving the data.
or use a generic view, because they are automatically passed the request context.
a simple direct to template generic view can be used to avoid having to import/pass in the request context.