New class based generic views and urlpatterns - django

with former generic views, I had something like this
link_info_dict = {
'queryset' : Link.objects.all(),
'date_field' : 'pub_date',
}
patterns('django.views.generic.date_based',
url(r'^links/$', 'archive_index', link_info_dict, 'coltrane_link_archive_index'),
....
)
Now with new class based generic views, I found that the following seems to work :
from django.views.generic.dates import ArchiveIndexView
....
urlpatterns = patterns('',
url(r'^links/$', ArchiveIndexView.as_view(**link_info_dict), name='coltrane_link_archive_index'),
....
)
I'm wondering if i'm doing things the best way.
Because I have to call the 'as_view' method, I have to import view first, and so I can't "factorize" the "django.views.generic.date_based". I'm actually using nearly all the date_based generic views. Is importing all those views at first and letting the patterns('' empty prefix the right approach ?
If I migrate all my apps to this new style of views, I'd prefer to do things the right way :)
Thanks

This looks fine - are you sure there isn't something else wrong? This lines up with the examples.
from django.views.generic.dates import ArchiveIndexView
from myapp.models import Article
urlpatterns = patterns('',
url(r'^archive/$',
ArchiveIndexView.as_view(model=Article, date_field="pub_date"),
name="article_archive"),
)
And it aligns with the documentation
Any arguments passed to as_view() will override attributes set on the class.

Related

Wagtail: Add method to PageModel to get an inspect url

I am trying to add a dynamic instead of a hard coded get_inspect_url method to a PageModel:
class MyModel(Page):
# ...
# this is what I have now, which breaks in prod due to a different admin url
def get_inspect_url(self):
inspect_url = f"/admin/myapp/mymodel/inspect/{self.id}/"
return inspect_url
I know that you can use the ModelAdmin.url_helper_class in wagtail_hooks.py to get admin urls for an object, but I can't figure a way to do the same on the model level as a class method. Is there a way?
thanks to #Andrey Maslov tip I found a way:
from django.urls import reverse
def get_inspect_url(self):
return reverse(
f"{self._meta.app_label}_{self._meta.model_name}_modeladmin_inspect",
args=[self.id],
)
The basic form of a Wagtail admin url is:
[app_label]_[model_name]_modeladmin_[action]
Just change the [action] to one of the following to generated other instance level admin urls:
edit
delete
if you have in you urls.py line like this
path(MyModel/view/<id:int>/', YourViewName, name='mymodel-info'),
then you can add to your models.py this lines
from django.urls import reverse
...
class MyModel(Page):
def get_inspect_url(self):
inspect_url = reverse('mymodel-info', kwargs={'id': self.id})
return inspect_url

how to have options in urls in django 2.0

In Django 1 I used to have url choices like this:
url('meeting/(?P<action>edit|delete)/', views.meeting_view, name='meeting'),
How I do this in Django 2.0 with the <> syntax:
Maybe something like this?
path('meeting/(<action:edit|delete>)/', views.meeting_view, name='meeting'),
If I understand the documentation, your first syntax should work right out-of-the-box.
Anyway here's how you could do with the new syntax:
First file = make a Python package converters and add edit_or_delete.py with that:
import re
class EditOrDeleteConverter:
regex = '(edit|delete)'
def to_python(self, value):
result = re.match(regex, value)
return result.group() if result is not None else ''
def to_url(self, value):
result = re.match(regex, value)
return result.group() if result is not None else ''
And for your urls.py file, this:
from django.urls import register_converter, path
from . import converters, views
register_converter(converters.EditOrDeleteConverter, 'edit_or_delete')
urlpatterns = [
path('meeting/<edit_or_delete:action>/', views.meeting_view, name='meeting'),
]
I would not use verbs in urls to indicate the purpose. Rather, rely on HTTP verbs such as GET, PUT, POST, DELETE and handle them in your view. That way, you can have just one view class handling all those different methods with just one URL.

how to get a list of all views in a django application?

Is there any way to get a list of all views in an django app? I have googled for answer. All answers shows a way to get list of urls.
Getting list of all the views of a Django project:
To get all the views present in a Django project, we create a function get_all_view_names() which takes urlpatterns as input and returns the complete list of views being used in the project as the output.
First, we import the root_urlconf module using settings.ROOT_URLCONF. Then root_urlconf.urls.urlpatterns will give us the list of project's urlpatterns.
The above urlpatterns list contains RegexURLPattern and RegexURLResolver objects. Accessing .urlpatterns on a RegexURLResolver will further give us a list of RegexURLPattern and RegexURLResolver objects.
A RegexURLPattern object will give us the view name which we are interested in. The callback attribute on it contains the callable view. When we pass either a string in our urls like 'foo_app.views.view_name' representing the path to a module and a view function name, or a callable view, then callback attribute is set to this. Further accessing .func_name will give us the view name.
We call the function get_all_view_names() recursively and add the view names obtained from a RegexURLPattern object to a global list VIEW_NAMES.
from django.conf import settings
from django.core.urlresolvers import RegexURLResolver, RegexURLPattern
root_urlconf = __import__(settings.ROOT_URLCONF) # import root_urlconf module
all_urlpatterns = root_urlconf.urls.urlpatterns # project's urlpatterns
VIEW_NAMES = [] # maintain a global list
def get_all_view_names(urlpatterns):
global VIEW_NAMES
for pattern in urlpatterns:
if isinstance(pattern, RegexURLResolver):
get_all_view_names(pattern.url_patterns) # call this function recursively
elif isinstance(pattern, RegexURLPattern):
view_name = pattern.callback.func_name # get the view name
VIEW_NAMES.append(view_name) # add the view to the global list
return VIEW_NAMES
get_all_view_names(all_urlpatterns)
Getting list of all the views in a Django application:
To get the list of all the views present in a Django application, we will use the get_all_view_names() function defined above.
We will first import all the urlpatterns of the application and pass this list to the get_all_view_names() function.
from my_app.urls import urlpatterns as my_app_urlpatterns # import urlpatterns of the app
my_app_views = get_all_view_names(my_app_urlpatterns) # call the function with app's urlpatterns as the argument
my_app_views gives us the list of all the views present in my_app Django app.
Adding on to above fix by Rahul, if anyone is using Python3, you will need to use __name__ instead of func_name:
...
view_name = pattern.callback.__name__
...
otherwise you will get the following:
AttributeError: 'function' object has no attribute 'get_all_view_names'
(Thanks to scipy-gitbot at https://github.com/scipy/scipy/issues/2101#issuecomment-17027406
As an alternative, if you are disinclined to using global variables, here is what I ended up using :
all_urlpatterns = __import__(settings.ROOT_URLCONF).urls.urlpatterns
detail_views_list = []
def get_all_view_names(urlpatterns):
for pattern in urlpatterns:
if isinstance(pattern, RegexURLResolver):
get_all_view_names(pattern.url_patterns)
elif isinstance(pattern, RegexURLPattern):
detail_views_list.append(pattern.callback.__name__)
get_all_view_names(all_urlpatterns)
all_views_list = []
# remove redundant entries and specific ones we don't care about
for each in detail_views_list:
if each not in "serve add_view change_view changelist_view history_view delete_view RedirectView":
if each not in all_views_list:
all_views_list.append(each)
Then you can just iterate through all_views_list to get the list of filtered views.
update: Mar 1 2018
In Django 2.0, django.core.urlresolvers is moved to django.urls. RegexURLPattern and RegexURLResolver are renamed to URLPattern and URLResolver. So you should use
from django.urls import URLResolver, URLPattern
instead of
from django.core.urlresolvers import RegexURLResolver, RegexURLPattern
if you are using Django 2.
Get all Django and DRF views w/o using global vars
def get_all_views(urlpatterns, views=None):
views = views or {}
for pattern in urlpatterns:
if hasattr(pattern, 'url_patterns'):
get_all_views(pattern.url_patterns, views=views)
else:
if hasattr(pattern.callback, 'cls'):
view = pattern.callback.cls
elif hasattr(pattern.callback, 'view_class'):
view = pattern.callback.view_class
else:
view = pattern.callback
views[pattern.name] = view
return views

How do I change the view a URL resolves to after a certain date?

I'm writing a contest app. The contest closes at midnight on a particular date. I want to have the app automatically switch from: using a CookieWizardView, from formtools; to a normal TemplateView, from the generic view library.
Currently the relevant part of my urlpatterns looks like this:
urlpatterns += patterns('',
url(r'^$', 'appname.views.contest'), # the CookieWizardView
)
and I'd like it, after a certain date, to act as though it looks like this:
urlpatterns += patterns('',
url(r'^$', 'appname.views.contestclosed'), # a TemplateView
)
I am totally, totally fine with having a hard-coded magic number, I just don't want to be up at midnight that day!
~~
I solved this but can't answer my own question because I'm too new.
I made a function in my views.py:
def contest_switcher(request):
if datetime.datetime.now() < datetime.datetime(YEAR_OVER, MONTH_OVER, DAY_OVER):
return contest(request)
else:
return contestclosed(request)
This does the trick, now my urlpattern is:
urlpatterns += patterns('',
url(r'^$', 'appname.views.contest_switcher'),
)
I did have to add a function to my contest closed view, though, because it wasn't expecting a POST, which could happen if someone is trying to fill out the contest form at midnight:
class ContestClosedView(TemplateView):
template_name = "appname/closed.html"
def post(self, *args, **kwargs):
return self.get(*args, **kwargs)
contestclosed = ContestClosedView.as_view()
You don't have to try to hack your urls.py to pull this off. Set one URL pattern that points to a view that looks like this:
def contest_page(request, contest_id):
try:
contest = Contest.objects.get(pk=contest_id)
except Contest.DoesNotExist:
raise Http404 # minimum necessary - you can do better
if datetime.datetime.now() < contest.end_date: # model field rather than module constants
return contest(request, contest_id) # CookieWizardView
else:
return contestclosed(request, contest_id) # TemplateView
This is basically your contest_switcher with improvements:
Applies to multiple contests
Contests know their own end date so you don't clutter your module scope with constants
Simple urls.py and the view does the work of delegating what is shown (you know, the view)
(Note that this example implies that you would change your models correspondingly and import all the correct libraries and such.)

merge 2 django (very similar) urlconfs in one

I have an app called "products" that manages "products" and "categories". And I have products/views.py (with generic views) that goes like this:
Objects = {
'products': {'model':Product, 'form':ProductForm}
'categories': {'model':Category, 'form':CategoryForm}
}
and something like this:
def list(request, obj):
model = Objects[obj]['model']
queryset = model.objects.all()
return object_list(request, queryset=queryset)
and then my project urls.py is something like this:
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^products/', include('products.product_urls.py'), {obj:'product'}),
(r'^categories/', include('products.category_urls.py'), {obj:'category'}),
)
and then I have the two urls.py for category and product like this:
1) products/product_urls.py
urlpatterns = patterns('',
url(r'^$', 'products.views', name='products-list'),
)
2) and a very similar line in products/category_urls.py
urlpatterns = patterns('',
url(r'^$', 'products.views', name='categories-list'),
)
As you can see, products/product_urls.py and products/category_urls.py are really very similar except for the url names.
My question is: is there a smart technique to "merge" products/product_urls.py and products/category_urls.py into a single module and still have different names for the urls depending on the "object" they're working on. i.e. have a single products/urls.py that'll manage both objects: product and category
You can maybe include the same url module twice, but use namespaced urls!
To me, this seems obvious:
from django.conf.urls.defaults import *
urlpatterns = patterns('',
url(r'^products/$', 'products.views.list', {'obj':'product'}, name='products-list'),
url(r'^categories/$', 'products.views.list', {'obj':'category'}, name='categories-list'),
)
The url function only differs from the tuple approach in that you can use keyword args (like name) in it.
Your code seems like it would break if you were to really try it verbatim. This, along with the perceived obviousness, makes me wonder if your actual use case is more complicated and requires a different answer.
Furthermore, the object-list generic view already employs the functionality you're trying to create with your Objects approach. (See the queryset argument; also, create-object's form_class arg). An example:
from django.conf.urls.defaults import *
from models import Product, Category
from django.views.generic.list_detail import object_list
urlpatterns = patterns('',
url(r'^products/$',
object_list,
{'queryset': Product.objects.all()},
name='products-list'),
url(r'^categories/$',
object_list,
{'queryset': Category.objects.all()},
name='categories-list'),
)