Django Admin - Adding custom views to model - python

So basically what I would like is to have a custom view (print view) for one of my model.
I added a custom button and I changed the get_url() of my model:
def get_urls(self):
urls = super(MyModelAdmin, self).get_urls()
my_urls = patterns('',
url(r'^/print/schedule$',
self.admin_site.admin_view(views.PrintScheduleDetailView.as_view()), name='print_schedule'),
)
return my_urls + urls
When I try to view it:
my_app/my_model/primary_key/print/schedule/
It tells me the object with the pk 1/print/schedule doesn't exist....
What can I do?
Thanks,
Ara
EDIT: Here is my view class:
class PrintScheduleDetailView(generic.DetailView):
model = Tournament
template_name = 'print/schedule.html'
I got the link to kinda work...
localhost/tournament/print/schedule/
works but it tells Generic detail view PrintScheduleDetailView must be called with either an object pk or a slug.... I tried adding a primary key localhost/tournament/print/schedule/pk but didnt work...
Thanks,
Ara

Perhaps the regular expression should be changed to remove the '^' symbol like:
url(r'/print/schedule$'

According to Detailview of class based views:
By default this requires self.queryset and a pk or slug argument
in the URLconf, but subclasses can override this to return any object.
url(r'^/(?P<pk>[\d]+)/print/schedule/$', self.admin_site.admin_view(views.PrintScheduleDetailView.as_view()), name='print_schedule'),)
I believe you need to pass the pk, except if you are fetching it through another way (you haven't pasted your Class View).

Related

How class-based views determine which template file will be called?

here's the code:
# urls.py
urlpatterns = [
path("books/", views.BookListView.as_view(), name="books"),
]
and the views
# views.py
class BookListView(generic.ListView):
model = Book
Book is a class in models.py, this view will using book_list.html template. My question is:
Why does it knows what templates will be called? I didnt even give the template_name to it. just like this
template_name = 'book_list.html'
It will look for the book_list.html because the default template_name_suffix property for that class is defined as _list, which means that if you do not define a template_name or template_name_suffix yourself, Django will look for the book_list.html template.
The book part comes from the fact that your model is called Book, it is then concatenated with template_name_suffix, in this case with _list and finally, .html is appended as file extension.
You can take a look at the actual code here. (ListView inherits from MultipleObjectTemplateResponseMixin.)
In ListView when you don't specify template_name explicitly, it takes lowercased models name and adds _list to the end. Like your model is Book and it will search for book_list.html
If you want your own suffix to be applied instead of _list you need to specify it like this:
template_name_suffix = '_myown'
In addition, this website is very helpful to see all methods and usage of Class-based views.
Generic Listview will look for the model name and suffix with '_list'. Have a look the below class that does this in Django
class MultipleObjectTemplateResponseMixin(TemplateResponseMixin):
"""
Mixin for responding with a template and list of objects.
"""
template_name_suffix = '_list'
def get_template_names(self):
"""
Return a list of template names to be used for the request. Must return
a list. May not be called if render_to_response is overridden.
"""
try:
names = super(MultipleObjectTemplateResponseMixin, self).get_template_names()
except ImproperlyConfigured:
# If template_name isn't specified, it's not a problem --
# we just start with an empty list.
names = []
# If the list is a queryset, we'll invent a template name based on the
# app and model name. This name gets put at the end of the template
# name list so that user-supplied names override the automatically-
# generated ones.
if hasattr(self.object_list, 'model'):
opts = self.object_list.model._meta
names.append("%s/%s%s.html" % (opts.app_label, opts.model_name, self.template_name_suffix))
return names

Is it required to add custom views in admin page in ModelAdmin class when we can do it normally by adding views in views.py and urls in urls.py?

According to django docs:
class MyModelAdmin(admin.ModelAdmin):
def get_urls(self):
urls = super(MyModelAdmin, self).get_urls()
my_urls = [
url(r'^my_view/$', self.my_view),
]
return my_urls + urls
def my_view(self, request):
# ...
context = dict(
# Include common variables for rendering the admin template.
self.admin_site.each_context(request),
# Anything else you want in the context...
key=value,
)
return TemplateResponse(request, "sometemplate.html", context)
If I am not wrong, we can do the same thing by adding url in urls.py and the views in views.py as it is normally done then, what is the use of introducing this way? I am a newbie to django and I may be missing something here.
Can you please provide an example where we cannot do it in views.py and we must use the above method?
Any guidance/help would be appreciated.
I think I figured out, both of them can be used to do the same thing but the key difference is that the views which you write using above method will belong to admin app while the general views in views.py belongs to the particular app in you have written.
Hence, the url in ModelAdmin need to be called using name admin:url_name since the url goes as admin/my_views/ in given example.

How to implement method in Django REST?

Have the next Django REST question.
I have the view.
class MessageViewSet(viewsets.ModelViewSet):
serializer_class = MessageSerializer
queryset = Message.objects.filter(isread = False)
def mark_read():
queryset = Message.objects.update(isread=True)
return Response({'read':queryset})
And router in urls.py
router = SimpleRouter() router.register(r'api/get_messages', MessageViewSet)
urlpatterns = [
url(r'^$', MainView.as_view(), name='main'),
url(r'^', include(router.urls)) ]
Now i have 'get_messages' page which shows all list.
How can i implement a method which would change 'isread' value of model instanse from False to True, when I visit a 'mark_read' page?
As you can see, i tried to write method in the class. But when i'm trying to call it in urls in this way:
router.register(r'api/mark_read', MessageViewSet.mark_read),
Here comes an error.
assert queryset is not None, 'base_name argument not specified, and could ' \
AssertionError: base_name argument not specified, and could not automatically determine the name from the viewset, as it does not have a .queryset attribute.
Maybe i shouldnt use router, and rewrite view and urls in other way. If u know how to solve this problem, please answer. Thanks.
You can use detail_route or list_route decorators.
from rest_framework.decorators import list_route
class MessageViewSet(viewsets.ModelViewSet):
#list_route()
def mark_read(self, request):
queryset = Message.objects.update(isread=True)
return Response({'read':queryset})
With that mark_read method will be available at api/get_messages/mark_read. And you don't need to create separate router, just use one you created for MessageViewSet
docs reference
Since you are using a model viewset you can directly use put or patch rest method to send the desired value for the desired field as the data.
Ideally in rest get should not change model values. If you really want a different end point put the list_route or detail_route decorator on your mark_read method, and make them a valid call for only a put and/or patch call
from rest_framework.decorators import list_route
class MessageViewSet(viewsets.ModelViewSet):
#list_route(methods=['Patch', 'PUT'])
def mark_read(self, request):
queryset = Message.objects.update(isread=True)
return Response({'read':queryset})
Thanks to #ivan-semochkin and #Shaumux for replies. Advices were really helpful.
That is my route. I used detail_route instead of list_route.
#detail_route(methods=['get','put'], url_name='mark_read/')
def mark_read(self, request, pk=None):
queryset = Message.objects.filter(pk=pk).update(isread=True)
return Response({'read':queryset})
Now 'isread' value is changing wnen i visit 'mark_read' page.
Link: "api/get_messages/pk/mark_read"
Does anyone know, is it posslible to make links looking the next way:
"api/get_messages" - list, "api/mark_read/pk" - changing isread value.
Is it possible to create something like this? "api/mark_read?=pk"

Refactoring Django class-based views, clean up 18 repetitive classes.

https://github.com/AnthonyBRoberts/fcclincoln/blob/master/apps/story/views.py
I'm a little embarrassed to admit that this is mine. But it is.
class FrontpageView(DetailView):
template_name = "welcome_content.html"
def get_object(self):
return get_object_or_404(Article, slug="front-page")
def get_context_data(self, **kwargs):
context = super(FrontpageView, self).get_context_data(**kwargs)
context['slug'] = "front-page"
events = Article.objects.filter(slug="events")
context['events'] = events
return context
So this is a pretty normal class-based detail view in Django.
It's assigning a template, getting an Article object, and adding some things to the context_data.
Then I copied this class 17 times. Each time, there's a different template, and a different slug, and different stuff added to the context_data.
The idea is that there's a WYSIWYG editor for administrators to change the web content, and a user authentication system, to allow multiple people access to the site content. Basically, a super-simple CMS, so no one has to edit html to update the site.
But I really wish I could refactor this so I don't have these nearly identical 18 classes. Any suggestions on where I should start on this would be most welcome.
Squash all of your classes down to a single class that inherits from TemplateResponseMixin, as DetailView does, (also check out the SingleObjectTemplateResponseMixin) and override its get_template_names() method to return the template appropriate for the current situation.
A beautiful example of this being used is in the django-blog-zinnia project
def get_template_names(self):
"""
Return a list of template names to be used for the view.
"""
model_type = self.get_model_type()
model_name = self.get_model_name()
templates = [
'zinnia/%s/%s/entry_list.html' % (model_type, model_name),
'zinnia/%s/%s_entry_list.html' % (model_type, model_name),
'zinnia/%s/entry_list.html' % model_type,
'zinnia/entry_list.html']
if self.template_name is not None:
templates.insert(0, self.template_name)
return templates
Django will take that list of names and try each item to see if it exists in the templates folder. If it does, that template is used.
Update
After looking at your code a little more closely, perhaps something like this:
In your main urls.py
# convert each url
url(r'^$', FrontpageView.as_view()),
url(r'^history/$', HistoryView.as_view()),
url(r'^calendar/$', CalendarView.as_view()),
url(r'^news/$', NewsView.as_view()),
url(r'^visitors/$', VisitorsView.as_view()),
...
# to just
url(r'^(?P<slug>[\w\d/-]+)/$', SuperSpecialAwesomeView.as_view()),
# but, put this at the end of urls list after any routes that don't use this view
DetailView, after setting the class attribute model, will check to see if slug is in the url's kwargs and if it is, it will use the slug to do a model lookup just like what you are already doing: Article.ojects.get(slug=self.kwargs['slug'])
models.py
You could add a type field to your Article model. The type will specify what type of article it is. For example, your ChildrenView, YouthView, and AdultView could all have a type of music (since the templates are all music, I'm assuming that's how they are related).
ARTICLE_TYPE_CHOICES = (
(0, 'music'),
(1, 'weddings'),
(2, 'outreach'),
...
)
class Article(models.Model):
...
type = models.IntegerField(choices=ARTICLE_TYPE_CHOICES)
...
Then, in your views.py
class SuperSpecialAwesomeView(DetailView):
template_name = None
model = Article
def get_template_names(self):
slug = self.kwargs.get('slug', '')
templates = [
# create a template based on just the slug
'{0}.html'.format(slug),
# create a template based on the model's type
'{0}.html'.format(self.object.get_type_display()),
]
# Allow for template_name overrides in subclasses
if self.template_name is not None:
templates.insert(0, self.template_name)
return templates
Given an article instance with a type of music and a slug of ministry/children, Django will look for a template named ministry/children.html and a template named music.html.
And if you need to do some special stuff for other views (like you will probably need to for SermonsView), then subclass SuperSpecialAwesomeView
class SermonsView(SuperSpecialAwesomeView):
paginate_by = 2
queryset = Article.objects.order_by('-publish_date')
A quick approach I would think:
Add a template field in the model with a list of predefined template choices (those can be created dynamically).
Override the default DetailView methods, override the get_template_names method to assign the proper template to the view (if not available fallback, that can be done through a try: except:).
Apart from that you can alter the View behaviour with any kind of model flags.
This way you can have a single entry point for a model, rather than defining repeatable views all over the place.
I tend to keep a FrontPageView independent from other views though, for easiness and because it serves a different purpose.
If you need repeatable context entries, consider a context processor, if you need repeatable context entries for specific views consider Mixins.
Rarely I can find a places I need to use CBD.
You can refactor it like this:
def editable_page(slug):
return {
'context': {
'slug': slug
}
'template': 'mysupertemplates/{0}.html'.format(slug)
}
def frontpage(req):
return editable_page('frontpage')
def chat(req):
return editable_page('char')
def about(req):
return editable_page('about')

How to use current logged in user as PK for Django DetailView?

When defining URL patterns, I am supposed to use a regular expression to acquire a PK from the URL.
What if I want a URL that has no PK, and if it's not provided, it will use the currently logged in user? Examples:
visiting /user will get a DetailView of the currently logged in user
/user/edit will show an UpdateView for the currently logged in user
I tried hard-coding the pk= in the Detail.as_view() call but it reports invalid keyword.
How do I specify that in the URL conf?
My sample code that shows PK required error when visiting /user URL:
urlpatterns = patterns('',
url(r'user/$',
DetailView.as_view(
model=Account,
template_name='user/detail.html')),
)`
An alternative approach would be overriding the get_object method of the DetailView subclass, something along the line of:
class CurrentUserDetailView(UserDetailView):
def get_object(self):
return self.request.user
Much cleaner, simpler and more in the spirit of the class-based views than the mixin approach.
EDIT: To clarify, I believe that two different URL patterns (i.e. one with a pk and the other without) should be defined separately in the urlconf. Therefore they could be served by two different views as well, especially as this makes the code cleaner. In this case the urlconf might look something like:
urlpatterns = patterns('',
url(r"^users/(?P<pk>\d+)/$", UserDetailView.as_view(), name="user_detail"),
url(r"^users/current/$", CurrentUserDetailView.as_view(), name="current_user_detail"),
url(r"^users/$", UserListView.as_view(), name="user_list"),
)
And I've updated my example above to note that it inherits the UserDetailView, which makes it even cleaner, and makes it clear what it really is: a special case of the parent view.
As far as I know, you can't define that on the URL definition, since you don't have access to that information.
However, what you can do is create your own mixin and use it to build views that behave like you want.
Your mixin would look something like this:
class CurrentUserMixin(object):
model = Account
def get_object(self, *args, **kwargs):
try:
obj = super(CurrentUserMixin, self).get_object(*args, **kwargs)
except AttributeError:
# SingleObjectMixin throws an AttributeError when no pk or slug
# is present on the url. In those cases, we use the current user
obj = self.request.user.account
return obj
and then, make your custom views:
class UserDetailView(CurrentUserMixin, DetailView):
pass
class UserUpdateView(CurrentUserMixin, UpdateView):
pass
Generic views uses always RequestContext. And this paragraph in the Django Documentation says that when using RequestContext with auth app, the template gets passed an user variable that represents current user logged in. So, go ahead, and feel free to reference user in your templates.
You can get the details of the current user from the request object. If you'd like to see a different user's details, you can pass the url as parameter. The url would be encoded like:
url(r'user/(?P<user_id>.*)$', 'views.user_details', name='user-details'),
views.user_details 2nd parameter would be user_id which is a string (you can change the regex in the url to restrict integer values, but the parameter would still of type string). Here's a list of other examples for url patterns from the Django documentation.

Categories

Resources