merge 2 django (very similar) urlconfs in one - django

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'),
)

Related

Django optional URL captured value for rest APIView

For following Django code using django_rest_framework:
class PollMessageView(generics.ListAPIView):
serializer_class = MessageSerializer
lookup_url_kwarg = 'count'
def get_queryset(self):
count = self.kwargs.get(self.lookup_url_kwarg)
queryset = Message.objects.all()[:count]
return queryset
urlpatterns = [
path('poll_message/<int:count>', PollMessageView.as_view(), name="poll_message"),
]
How do I make the count parameter optional in the URL pattern? For example, if I visit /poll_message/ without a count, it would still call the PollMessageView (and I can set a default number for count if it's missing)?
create a new path as ,
urlpatterns = [
path('poll_message/', PollMessageView.as_view(), name="poll_message-wo-count"),
path('poll_message/<int:count>', PollMessageView.as_view(), name="poll_message"),
]
# URLconf
from django.urls import path
from . import views
urlpatterns = [
path('blog/', views.page),
path('blog/page<int:num>/', views.page),
]
# View (in blog/views.py)
def page(request, num=1):
# Output the appropriate page of blog entries, according to num.
...
In detail reference - https://docs.djangoproject.com/en/3.0/topics/http/urls/#specifying-defaults-for-view-arguments
You can specify defaults for view arguments, but the example on the website is for functional view, not sure how would that work for class based view or in this case, the rest framework class based view.
However, you can add another path with extra options to achieve the same thing
path('poll_message/', PollMessageView.as_view(), {
"count": 5
}, name="poll_message"),

Using multiple slugs in a url

I want to keep my urls dynamic as well as clean.
Therefore I'm using slugs.
My problem right now is, that I get the following error:
redefinition of group name 'slug' as group 2; was group 1 at position 42
I think I get that error, because I have two slugs in my chain.
For reference I have a ListView into ListView into an UpdateView, alls importet from django.views.generic. The first list view gives me the first slug and the update view the second.
Here is the url pattern (spread across the apps):
First list view:
urlpatterns = [
url(r'^$', RestaurantListView.as_view(), name='restaurant-list'),
url(r'^(?P<slug>[\w-]+)/menus/', include('menu.urls', namespace='menu')),
]
Second list view:
urlpatterns = [
url(r'^$', MenuListView.as_view(), name='menu-list'),
url(r'^(?P<slug>[\w-]+)/', MenuUpdateView.as_view(), name='menu-detail'),
]
In the templates I get the objects via:
<li><a href='{{obj.get_absolute_url}}'> {{obj}} </a></li>
Which I have defined in the respective models:
def get_absolute_url(self):
return reverse('restaurants:menu:menu-detail', kwargs={'slug': self.slug})
and
def get_absolute_url(self):
return reverse('restaurants:menu:menu-list', kwargs={'slug': self.slug})
So the resulting pattern at the end is:
restaurants/(?P<slug>[\w-]+)/menus/(?P<slug>[\w-]+)/
How can I fix it so that I don't get the error anymore?
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^articles/2003/$', views.special_case_2003),
url(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive),
url(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]{2})/$', views.article_detail),
]
for more details https://docs.djangoproject.com/en/1.11/topics/http/urls/

Passing pattern to url

I want to map two or more seller to the same method ecommerce.views.seller. Below is the working code:
urlpatterns = patterns('',
url(r'^(?:store1|store3)/$', 'ecommerce.views.seller'),
)
Is there any way by which I can declare some variable with pattern and simply pass it into urlpatterns. Something like:
SELLER_ID = '?:store1|store3'
urlpatterns = patterns('',
url(r'^(SELLER_ID)/$', 'ecommerce.views.seller'),
)
Just use regular string formatting syntax:
url(r'^({})/$'.format(SELLER_ID), 'ecommerce.views.seller')
You should use capturing groups for regex path variables in order to have them provided as keyword arguments in your view method:
https://docs.djangoproject.com/en/1.10/topics/http/urls/#specifying-defaults-for-view-arguments
There is a very short example at the above link.
What you would probably want to do:
urlpatterns = patterns('',
url(r'^store(?P<pk>[0-9]+)/$', 'ecommerce.views.seller'),
)
in ecommerce/views.py:
def seller(request, pk):
seller = get_object_or_404(Store, pk=pk) # if DB object
# or if not in DB then just use the number
# do your stuff
return response
or use a generic view if the PK points to a DB model:
urlpatterns = patterns('',
url(r'^store(?P<pk>[0-9]+)/$', StoreDetailView.as_view(), name='store_detail'),
)
class StoreDetailView(DetailView):
model = Store
# the rest is django magic, you just have to provide the template

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.)

New class based generic views and urlpatterns

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.